From aa523a9b4b42662c721fc747aa2cc278797195f2 Mon Sep 17 00:00:00 2001 From: Rune Tynan Date: Thu, 13 Jan 2022 12:38:08 -0500 Subject: [PATCH 01/69] Fix errors on blanket impls by ignoring the children of their generated implementations --- src/librustdoc/clean/types.rs | 8 ++++++++ src/librustdoc/json/mod.rs | 6 +++++- src/test/rustdoc-json/impls/blanket_with_local.rs | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/test/rustdoc-json/impls/blanket_with_local.rs diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 00c6e38839f54..c47a2c7ca03fa 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -75,6 +75,14 @@ impl ItemId { } } + #[inline] + crate fn is_local_impl(self) -> bool { + match self { + ItemId::Blanket { impl_id, .. } => impl_id.is_local(), + ItemId::Auto { .. } | ItemId::DefId(_) | ItemId::Primitive(_, _) => false, + } + } + #[inline] #[track_caller] crate fn expect_def_id(self) -> DefId { diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 88a5c0c5ca260..c1efefc322e0e 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -172,7 +172,11 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { /// implementations filled out before they're inserted. fn item(&mut self, item: clean::Item) -> Result<(), Error> { // Flatten items that recursively store other items - item.kind.inner_items().for_each(|i| self.item(i.clone()).unwrap()); + // We skip local blanket implementations, as we'll have already seen the actual generic + // impl, and the generated ones don't need documenting. + if !item.def_id.is_local_impl() { + item.kind.inner_items().for_each(|i| self.item(i.clone()).unwrap()); + } let id = item.def_id; if let Some(mut new_item) = self.convert_item(item) { diff --git a/src/test/rustdoc-json/impls/blanket_with_local.rs b/src/test/rustdoc-json/impls/blanket_with_local.rs new file mode 100644 index 0000000000000..88d1477f0b2da --- /dev/null +++ b/src/test/rustdoc-json/impls/blanket_with_local.rs @@ -0,0 +1,14 @@ +// Test for the ICE in rust/83718 +// A blanket impl plus a local type together shouldn't result in mismatched ID issues + +// @has method_abi.json "$.index[*][?(@.name=='Load')]" +pub trait Load { + fn load() {} +} + +impl

Load for P { + fn load() {} +} + +// @has - "$.index[*][?(@.name=='Wrapper')]" +pub struct Wrapper {} From 74f0e582becf704e6e9ccf0c1956bcbc7cc3703d Mon Sep 17 00:00:00 2001 From: Rune Tynan Date: Thu, 13 Jan 2022 13:25:11 -0500 Subject: [PATCH 02/69] Fix typo in test --- src/test/rustdoc-json/impls/blanket_with_local.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/rustdoc-json/impls/blanket_with_local.rs b/src/test/rustdoc-json/impls/blanket_with_local.rs index 88d1477f0b2da..963ea2fe5aea8 100644 --- a/src/test/rustdoc-json/impls/blanket_with_local.rs +++ b/src/test/rustdoc-json/impls/blanket_with_local.rs @@ -1,7 +1,7 @@ // Test for the ICE in rust/83718 // A blanket impl plus a local type together shouldn't result in mismatched ID issues -// @has method_abi.json "$.index[*][?(@.name=='Load')]" +// @has blanket_with_local.json "$.index[*][?(@.name=='Load')]" pub trait Load { fn load() {} } From a6aa3cb2c10c986eb231378e154766ce38f99a49 Mon Sep 17 00:00:00 2001 From: Rune Tynan Date: Thu, 13 Jan 2022 14:40:28 -0500 Subject: [PATCH 03/69] inline ItemId method, clarify comments a bit --- src/librustdoc/clean/types.rs | 8 -------- src/librustdoc/json/mod.rs | 13 ++++++++++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index c47a2c7ca03fa..00c6e38839f54 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -75,14 +75,6 @@ impl ItemId { } } - #[inline] - crate fn is_local_impl(self) -> bool { - match self { - ItemId::Blanket { impl_id, .. } => impl_id.is_local(), - ItemId::Auto { .. } | ItemId::DefId(_) | ItemId::Primitive(_, _) => false, - } - } - #[inline] #[track_caller] crate fn expect_def_id(self) -> DefId { diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index c1efefc322e0e..0f80c8a5b4cc4 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -171,10 +171,17 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { /// the hashmap because certain items (traits and types) need to have their mappings for trait /// implementations filled out before they're inserted. fn item(&mut self, item: clean::Item) -> Result<(), Error> { + // We skip children of local blanket implementations, as we'll have already seen the actual + // generic impl, and the generated ones don't need documenting. + let local_blanket_impl = match item.def_id { + clean::ItemId::Blanket { impl_id, .. } => impl_id.is_local(), + clean::ItemId::Auto { .. } + | clean::ItemId::DefId(_) + | clean::ItemId::Primitive(_, _) => false, + }; + // Flatten items that recursively store other items - // We skip local blanket implementations, as we'll have already seen the actual generic - // impl, and the generated ones don't need documenting. - if !item.def_id.is_local_impl() { + if !local_blanket_impl { item.kind.inner_items().for_each(|i| self.item(i.clone()).unwrap()); } From aafcbf1e70defb145542ec66a98d581c23899e76 Mon Sep 17 00:00:00 2001 From: Rune Tynan Date: Thu, 13 Jan 2022 14:43:32 -0500 Subject: [PATCH 04/69] Update comment to make it a FIXME --- src/librustdoc/json/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 0f80c8a5b4cc4..c95570c4b3b2b 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -171,8 +171,10 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { /// the hashmap because certain items (traits and types) need to have their mappings for trait /// implementations filled out before they're inserted. fn item(&mut self, item: clean::Item) -> Result<(), Error> { - // We skip children of local blanket implementations, as we'll have already seen the actual - // generic impl, and the generated ones don't need documenting. + // FIXME(CraftSpider): We skip children of local blanket implementations, as we'll have + // already seen the actual generic impl, and the generated ones don't need documenting. + // This is necessary due to the visibility, return type, and self arg of the generated + // impls not quite matching, and will no longer be necessary when the mismatch is fixed. let local_blanket_impl = match item.def_id { clean::ItemId::Blanket { impl_id, .. } => impl_id.is_local(), clean::ItemId::Auto { .. } From 474e091160a5704ba6c07dee2d7aa789736ca857 Mon Sep 17 00:00:00 2001 From: Rune Tynan Date: Thu, 13 Jan 2022 14:46:04 -0500 Subject: [PATCH 05/69] Move FIXME to if statement --- src/librustdoc/json/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index c95570c4b3b2b..7f9d04d237eee 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -171,10 +171,6 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { /// the hashmap because certain items (traits and types) need to have their mappings for trait /// implementations filled out before they're inserted. fn item(&mut self, item: clean::Item) -> Result<(), Error> { - // FIXME(CraftSpider): We skip children of local blanket implementations, as we'll have - // already seen the actual generic impl, and the generated ones don't need documenting. - // This is necessary due to the visibility, return type, and self arg of the generated - // impls not quite matching, and will no longer be necessary when the mismatch is fixed. let local_blanket_impl = match item.def_id { clean::ItemId::Blanket { impl_id, .. } => impl_id.is_local(), clean::ItemId::Auto { .. } @@ -183,6 +179,10 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { }; // Flatten items that recursively store other items + // FIXME(CraftSpider): We skip children of local blanket implementations, as we'll have + // already seen the actual generic impl, and the generated ones don't need documenting. + // This is necessary due to the visibility, return type, and self arg of the generated + // impls not quite matching, and will no longer be necessary when the mismatch is fixed. if !local_blanket_impl { item.kind.inner_items().for_each(|i| self.item(i.clone()).unwrap()); } From 4be32f896a7fc7e9db8b92132b147870bd57bc9b Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 8 Oct 2021 15:09:20 -0700 Subject: [PATCH 06/69] Add test case for #57478 --- src/test/ui/generator/issue-57478.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/test/ui/generator/issue-57478.rs diff --git a/src/test/ui/generator/issue-57478.rs b/src/test/ui/generator/issue-57478.rs new file mode 100644 index 0000000000000..592632cd35102 --- /dev/null +++ b/src/test/ui/generator/issue-57478.rs @@ -0,0 +1,14 @@ +#![feature(negative_impls, generators)] + +struct Foo; +impl !Send for Foo {} + +fn main() { + assert_send(|| { + let guard = Foo; + drop(guard); + yield; + }) +} + +fn assert_send(_: T) {} From f712df8c5dfa14a01525dc28f38d731eedcc7263 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 20 Oct 2021 16:42:53 -0700 Subject: [PATCH 07/69] Track drop points in generator_interior This change adds the basic infrastructure for tracking drop ranges in generator interior analysis, which allows us to exclude dropped types from the generator type. Not yet complete, but many of the async/await and generator tests pass. The main missing piece is tracking branching control flow (e.g. around an `if` expression). The patch does include support, however, for multiple yields in th e same block. Issue #57478 --- compiler/rustc_passes/src/region.rs | 1 - .../src/check/generator_interior.rs | 190 +++++++++++++++--- src/test/ui/async-await/async-fn-nonsend.rs | 8 +- .../ui/async-await/unresolved_type_param.rs | 16 -- .../async-await/unresolved_type_param.stderr | 50 +---- src/test/ui/generator/issue-57478.rs | 2 + 6 files changed, 168 insertions(+), 99 deletions(-) diff --git a/compiler/rustc_passes/src/region.rs b/compiler/rustc_passes/src/region.rs index db699a56645c2..f8989200d7e08 100644 --- a/compiler/rustc_passes/src/region.rs +++ b/compiler/rustc_passes/src/region.rs @@ -255,7 +255,6 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h hir::ExprKind::AssignOp(..) | hir::ExprKind::Index(..) | hir::ExprKind::Unary(..) - | hir::ExprKind::Call(..) | hir::ExprKind::MethodCall(..) => { // FIXME(https://github.com/rust-lang/rfcs/issues/811) Nested method calls // diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index fb6e11dbfb738..a406a1a8ecd99 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,7 +3,10 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. +use crate::expr_use_visitor::{self, ExprUseVisitor}; + use super::FnCtxt; +use hir::HirIdMap; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -34,6 +37,7 @@ struct InteriorVisitor<'a, 'tcx> { guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>, guard_bindings_set: HirIdSet, linted_values: HirIdSet, + drop_ranges: HirIdMap, } impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { @@ -48,9 +52,11 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { ) { use rustc_span::DUMMY_SP; + let ty = self.fcx.resolve_vars_if_possible(ty); + debug!( - "generator_interior: attempting to record type {:?} {:?} {:?} {:?}", - ty, scope, expr, source_span + "attempting to record type ty={:?}; hir_id={:?}; scope={:?}; expr={:?}; source_span={:?}; expr_count={:?}", + ty, hir_id, scope, expr, source_span, self.expr_count, ); let live_across_yield = scope @@ -68,6 +74,14 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { yield_data.expr_and_pat_count, self.expr_count, source_span ); + match self.drop_ranges.get(&hir_id) { + Some(range) if range.contains(yield_data.expr_and_pat_count) => { + debug!("value is dropped at yield point; not recording"); + return None + } + _ => (), + } + // If it is a borrowing happening in the guard, // it needs to be recorded regardless because they // do live across this yield point. @@ -85,7 +99,6 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { }); if let Some(yield_data) = live_across_yield { - let ty = self.fcx.resolve_vars_if_possible(ty); debug!( "type in expr = {:?}, scope = {:?}, type = {:?}, count = {}, yield_span = {:?}", expr, scope, ty, self.expr_count, yield_data.span @@ -154,7 +167,6 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { self.expr_count, expr.map(|e| e.span) ); - let ty = self.fcx.resolve_vars_if_possible(ty); if let Some((unresolved_type, unresolved_type_span)) = self.fcx.unresolved_type_vars(&ty) { @@ -166,6 +178,39 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { } } } + + fn visit_call( + &mut self, + call_expr: &'tcx Expr<'tcx>, + callee: &'tcx Expr<'tcx>, + args: &'tcx [Expr<'tcx>], + ) { + match &callee.kind { + ExprKind::Path(qpath) => { + let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id); + match res { + // Direct calls never need to keep the callee `ty::FnDef` + // ZST in a temporary, so skip its type, just in case it + // can significantly complicate the generator type. + Res::Def( + DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn), + _, + ) => { + // NOTE(eddyb) this assumes a path expression has + // no nested expressions to keep track of. + self.expr_count += 1; + + // Record the rest of the call expression normally. + for arg in args { + self.visit_expr(arg); + } + } + _ => intravisit::walk_expr(self, call_expr), + } + } + _ => intravisit::walk_expr(self, call_expr), + } + } } pub fn resolve_interior<'a, 'tcx>( @@ -176,6 +221,20 @@ pub fn resolve_interior<'a, 'tcx>( kind: hir::GeneratorKind, ) { let body = fcx.tcx.hir().body(body_id); + + let mut drop_range_visitor = DropRangeVisitor::default(); + + // Run ExprUseVisitor to find where values are consumed. + ExprUseVisitor::new( + &mut drop_range_visitor, + &fcx.infcx, + def_id.expect_local(), + fcx.param_env, + &fcx.typeck_results.borrow(), + ) + .consume_body(body); + intravisit::walk_body(&mut drop_range_visitor, body); + let mut visitor = InteriorVisitor { fcx, types: FxIndexSet::default(), @@ -186,6 +245,7 @@ pub fn resolve_interior<'a, 'tcx>( guard_bindings: <_>::default(), guard_bindings_set: <_>::default(), linted_values: <_>::default(), + drop_ranges: drop_range_visitor.drop_ranges, }; intravisit::walk_body(&mut visitor, body); @@ -313,32 +373,9 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut guard_borrowing_from_pattern = false; + match &expr.kind { - ExprKind::Call(callee, args) => match &callee.kind { - ExprKind::Path(qpath) => { - let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id); - match res { - // Direct calls never need to keep the callee `ty::FnDef` - // ZST in a temporary, so skip its type, just in case it - // can significantly complicate the generator type. - Res::Def( - DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn), - _, - ) => { - // NOTE(eddyb) this assumes a path expression has - // no nested expressions to keep track of. - self.expr_count += 1; - - // Record the rest of the call expression normally. - for arg in *args { - self.visit_expr(arg); - } - } - _ => intravisit::walk_expr(self, expr), - } - } - _ => intravisit::walk_expr(self, expr), - }, + ExprKind::Call(callee, args) => self.visit_call(expr, callee, args), ExprKind::Path(qpath) => { intravisit::walk_expr(self, expr); let res = self.fcx.typeck_results.borrow().qpath_res(qpath, expr.hir_id); @@ -617,3 +654,98 @@ fn check_must_not_suspend_def( } false } + +/// This struct facilitates computing the ranges for which a place is uninitialized. +#[derive(Default)] +struct DropRangeVisitor { + consumed_places: HirIdSet, + drop_ranges: HirIdMap, + expr_count: usize, +} + +impl DropRangeVisitor { + fn record_drop(&mut self, hir_id: HirId) { + debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); + self.drop_ranges.insert(hir_id, DropRange { dropped_at: self.expr_count }); + } + + /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all + /// expressions. This method consumes a little deeper into the expression when needed. + fn consume_expr(&mut self, expr: &hir::Expr<'_>) { + self.record_drop(expr.hir_id); + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + self.record_drop(*hir_id); + } + _ => (), + } + } +} + +impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor { + fn consume( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + diag_expr_id: hir::HirId, + ) { + debug!("consume {:?}; diag_expr_id={:?}", place_with_id, diag_expr_id); + self.consumed_places.insert(place_with_id.hir_id); + } + + fn borrow( + &mut self, + _place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + _bk: rustc_middle::ty::BorrowKind, + ) { + } + + fn mutate( + &mut self, + _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + ) { + } + + fn fake_read( + &mut self, + _place: expr_use_visitor::Place<'tcx>, + _cause: rustc_middle::mir::FakeReadCause, + _diag_expr_id: hir::HirId, + ) { + } +} + +impl<'tcx> Visitor<'tcx> for DropRangeVisitor { + type Map = intravisit::ErasedMap<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, expr: &Expr<'_>) { + intravisit::walk_expr(self, expr); + + self.expr_count += 1; + + if self.consumed_places.contains(&expr.hir_id) { + self.consume_expr(expr); + } + } +} + +struct DropRange { + /// The post-order id of the point where this expression is dropped. + /// + /// We can consider the value dropped at any post-order id greater than dropped_at. + dropped_at: usize, +} + +impl DropRange { + fn contains(&self, id: usize) -> bool { + id >= self.dropped_at + } +} diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index 845941200fc95..4dd36e7f0f062 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -18,7 +18,7 @@ async fn fut() {} async fn fut_arg(_: T) {} async fn local_dropped_before_await() { - // FIXME: it'd be nice for this to be allowed in a `Send` `async fn` + // this is okay now because of the drop let x = non_send(); drop(x); fut().await; @@ -36,7 +36,7 @@ async fn non_send_temporary_in_match() { } async fn non_sync_with_method_call() { - // FIXME: it'd be nice for this to work. + let f: &mut std::fmt::Formatter = panic!(); if non_sync().fmt(f).unwrap() == () { fut().await; @@ -47,9 +47,9 @@ fn assert_send(_: impl Send) {} pub fn pass_assert() { assert_send(local_dropped_before_await()); - //~^ ERROR future cannot be sent between threads safely + assert_send(non_send_temporary_in_match()); //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_method_call()); - //~^ ERROR future cannot be sent between threads safely + } diff --git a/src/test/ui/async-await/unresolved_type_param.rs b/src/test/ui/async-await/unresolved_type_param.rs index 85d868c27032e..79c043b701ddb 100644 --- a/src/test/ui/async-await/unresolved_type_param.rs +++ b/src/test/ui/async-await/unresolved_type_param.rs @@ -8,24 +8,8 @@ async fn bar() -> () {} async fn foo() { bar().await; //~^ ERROR type inside `async fn` body must be known in this context - //~| ERROR type inside `async fn` body must be known in this context - //~| ERROR type inside `async fn` body must be known in this context - //~| ERROR type inside `async fn` body must be known in this context - //~| ERROR type inside `async fn` body must be known in this context //~| NOTE cannot infer type for type parameter `T` - //~| NOTE cannot infer type for type parameter `T` - //~| NOTE cannot infer type for type parameter `T` - //~| NOTE cannot infer type for type parameter `T` - //~| NOTE cannot infer type for type parameter `T` - //~| NOTE the type is part of the `async fn` body because of this `await` - //~| NOTE the type is part of the `async fn` body because of this `await` //~| NOTE the type is part of the `async fn` body because of this `await` - //~| NOTE the type is part of the `async fn` body because of this `await` - //~| NOTE the type is part of the `async fn` body because of this `await` - //~| NOTE in this expansion of desugaring of `await` - //~| NOTE in this expansion of desugaring of `await` - //~| NOTE in this expansion of desugaring of `await` - //~| NOTE in this expansion of desugaring of `await` //~| NOTE in this expansion of desugaring of `await` } fn main() {} diff --git a/src/test/ui/async-await/unresolved_type_param.stderr b/src/test/ui/async-await/unresolved_type_param.stderr index 8c0ecb8785d33..853e53ed69df2 100644 --- a/src/test/ui/async-await/unresolved_type_param.stderr +++ b/src/test/ui/async-await/unresolved_type_param.stderr @@ -10,54 +10,6 @@ note: the type is part of the `async fn` body because of this `await` LL | bar().await; | ^^^^^^ -error[E0698]: type inside `async fn` body must be known in this context - --> $DIR/unresolved_type_param.rs:9:5 - | -LL | bar().await; - | ^^^ cannot infer type for type parameter `T` declared on the function `bar` - | -note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:10 - | -LL | bar().await; - | ^^^^^^ - -error[E0698]: type inside `async fn` body must be known in this context - --> $DIR/unresolved_type_param.rs:9:5 - | -LL | bar().await; - | ^^^ cannot infer type for type parameter `T` declared on the function `bar` - | -note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:10 - | -LL | bar().await; - | ^^^^^^ - -error[E0698]: type inside `async fn` body must be known in this context - --> $DIR/unresolved_type_param.rs:9:5 - | -LL | bar().await; - | ^^^ cannot infer type for type parameter `T` declared on the function `bar` - | -note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:10 - | -LL | bar().await; - | ^^^^^^ - -error[E0698]: type inside `async fn` body must be known in this context - --> $DIR/unresolved_type_param.rs:9:5 - | -LL | bar().await; - | ^^^ cannot infer type for type parameter `T` declared on the function `bar` - | -note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:10 - | -LL | bar().await; - | ^^^^^^ - -error: aborting due to 5 previous errors +error: aborting due to previous error For more information about this error, try `rustc --explain E0698`. diff --git a/src/test/ui/generator/issue-57478.rs b/src/test/ui/generator/issue-57478.rs index 592632cd35102..39710febdb95c 100644 --- a/src/test/ui/generator/issue-57478.rs +++ b/src/test/ui/generator/issue-57478.rs @@ -1,3 +1,5 @@ +// check-pass + #![feature(negative_impls, generators)] struct Foo; From c4dee401700170c95c649682d62ad150d6b5fdeb Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 22 Oct 2021 12:45:02 -0700 Subject: [PATCH 08/69] Track drops across multiple yields --- compiler/rustc_middle/src/middle/region.rs | 6 +-- compiler/rustc_passes/src/region.rs | 11 ++++- .../src/check/generator_interior.rs | 46 +++++++++---------- src/test/ui/generator/drop-yield-twice.rs | 15 ++++++ src/test/ui/generator/drop-yield-twice.stderr | 25 ++++++++++ 5 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 src/test/ui/generator/drop-yield-twice.rs create mode 100644 src/test/ui/generator/drop-yield-twice.stderr diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index 39ca41c92ff75..75dd223d014d4 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -308,7 +308,7 @@ pub struct ScopeTree { /// The reason is that semantically, until the `box` expression returns, /// the values are still owned by their containing expressions. So /// we'll see that `&x`. - pub yield_in_scope: FxHashMap, + pub yield_in_scope: FxHashMap>, /// The number of visit_expr and visit_pat calls done in the body. /// Used to sanity check visit_expr/visit_pat call count when @@ -423,8 +423,8 @@ impl ScopeTree { /// Checks whether the given scope contains a `yield`. If so, /// returns `Some(YieldData)`. If not, returns `None`. - pub fn yield_in_scope(&self, scope: Scope) -> Option { - self.yield_in_scope.get(&scope).cloned() + pub fn yield_in_scope(&self, scope: Scope) -> Option<&Vec> { + self.yield_in_scope.get(&scope) } /// Gives the number of expressions visited in a body. diff --git a/compiler/rustc_passes/src/region.rs b/compiler/rustc_passes/src/region.rs index f8989200d7e08..8b22c46f01ba6 100644 --- a/compiler/rustc_passes/src/region.rs +++ b/compiler/rustc_passes/src/region.rs @@ -365,7 +365,8 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h let target_scopes = visitor.fixup_scopes.drain(start_point..); for scope in target_scopes { - let mut yield_data = visitor.scope_tree.yield_in_scope.get_mut(&scope).unwrap(); + let mut yield_data = + visitor.scope_tree.yield_in_scope.get_mut(&scope).unwrap().last_mut().unwrap(); let count = yield_data.expr_and_pat_count; let span = yield_data.span; @@ -428,7 +429,13 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h }; let data = YieldData { span, expr_and_pat_count: visitor.expr_and_pat_count, source: *source }; - visitor.scope_tree.yield_in_scope.insert(scope, data); + match visitor.scope_tree.yield_in_scope.get_mut(&scope) { + Some(yields) => yields.push(data), + None => { + visitor.scope_tree.yield_in_scope.insert(scope, vec![data]); + } + } + if visitor.pessimistic_yield { debug!("resolve_expr in pessimistic_yield - marking scope {:?} for fixup", scope); visitor.fixup_scopes.push(scope); diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index a406a1a8ecd99..ba4080031a213 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -69,29 +69,29 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { // // See the mega-comment at `yield_in_scope` for a proof. - debug!( - "comparing counts yield: {} self: {}, source_span = {:?}", - yield_data.expr_and_pat_count, self.expr_count, source_span - ); - - match self.drop_ranges.get(&hir_id) { - Some(range) if range.contains(yield_data.expr_and_pat_count) => { - debug!("value is dropped at yield point; not recording"); - return None - } - _ => (), - } - - // If it is a borrowing happening in the guard, - // it needs to be recorded regardless because they - // do live across this yield point. - if guard_borrowing_from_pattern - || yield_data.expr_and_pat_count >= self.expr_count - { - Some(yield_data) - } else { - None - } + yield_data + .iter() + .find(|yield_data| { + debug!( + "comparing counts yield: {} self: {}, source_span = {:?}", + yield_data.expr_and_pat_count, self.expr_count, source_span + ); + + match self.drop_ranges.get(&hir_id) { + Some(range) if range.contains(yield_data.expr_and_pat_count) => { + debug!("value is dropped at yield point; not recording"); + return false; + } + _ => (), + } + + // If it is a borrowing happening in the guard, + // it needs to be recorded regardless because they + // do live across this yield point. + guard_borrowing_from_pattern + || yield_data.expr_and_pat_count >= self.expr_count + }) + .cloned() }) }) .unwrap_or_else(|| { diff --git a/src/test/ui/generator/drop-yield-twice.rs b/src/test/ui/generator/drop-yield-twice.rs new file mode 100644 index 0000000000000..f484cbb8d67d5 --- /dev/null +++ b/src/test/ui/generator/drop-yield-twice.rs @@ -0,0 +1,15 @@ +#![feature(negative_impls, generators)] + +struct Foo(i32); +impl !Send for Foo {} + +fn main() { + assert_send(|| { //~ ERROR generator cannot be sent between threads safely + let guard = Foo(42); + yield; + drop(guard); + yield; + }) +} + +fn assert_send(_: T) {} diff --git a/src/test/ui/generator/drop-yield-twice.stderr b/src/test/ui/generator/drop-yield-twice.stderr new file mode 100644 index 0000000000000..f821f2f40055f --- /dev/null +++ b/src/test/ui/generator/drop-yield-twice.stderr @@ -0,0 +1,25 @@ +error: generator cannot be sent between threads safely + --> $DIR/drop-yield-twice.rs:7:5 + | +LL | assert_send(|| { + | ^^^^^^^^^^^ generator is not `Send` + | + = help: within `[generator@$DIR/drop-yield-twice.rs:7:17: 12:6]`, the trait `Send` is not implemented for `Foo` +note: generator is not `Send` as this value is used across a yield + --> $DIR/drop-yield-twice.rs:9:9 + | +LL | let guard = Foo(42); + | ----- has type `Foo` which is not `Send` +LL | yield; + | ^^^^^ yield occurs here, with `guard` maybe used later +... +LL | }) + | - `guard` is later dropped here +note: required by a bound in `assert_send` + --> $DIR/drop-yield-twice.rs:15:19 + | +LL | fn assert_send(_: T) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to previous error + From f664cfc47cdfaa83a1fd35e6e6a3fcdb692286ae Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 22 Oct 2021 15:49:38 -0700 Subject: [PATCH 09/69] Make generator and async-await tests pass The main change needed to make this work is to do a pessimistic over- approximation for AssignOps. The existing ScopeTree analysis in region.rs works by doing both left to right and right to left order and then choosing the most conservative ordering. This behavior is needed because AssignOp's evaluation order depends on whether it is a primitive type or an overloaded operator, which runs as a method call. This change mimics the same behavior as region.rs in generator_interior.rs. Issue #57478 --- .../src/check/generator_interior.rs | 139 +++++++++++++----- src/test/ui/async-await/async-fn-nonsend.rs | 5 +- 2 files changed, 106 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index ba4080031a213..baeb78139ac96 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -15,6 +15,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::hir_id::HirIdSet; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind}; +use rustc_middle::hir::place::{Place, PlaceBase}; use rustc_middle::middle::region::{self, YieldData}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::symbol::sym; @@ -222,30 +223,37 @@ pub fn resolve_interior<'a, 'tcx>( ) { let body = fcx.tcx.hir().body(body_id); - let mut drop_range_visitor = DropRangeVisitor::default(); - - // Run ExprUseVisitor to find where values are consumed. - ExprUseVisitor::new( - &mut drop_range_visitor, - &fcx.infcx, - def_id.expect_local(), - fcx.param_env, - &fcx.typeck_results.borrow(), - ) - .consume_body(body); - intravisit::walk_body(&mut drop_range_visitor, body); - - let mut visitor = InteriorVisitor { - fcx, - types: FxIndexSet::default(), - region_scope_tree: fcx.tcx.region_scope_tree(def_id), - expr_count: 0, - kind, - prev_unresolved_span: None, - guard_bindings: <_>::default(), - guard_bindings_set: <_>::default(), - linted_values: <_>::default(), - drop_ranges: drop_range_visitor.drop_ranges, + let mut visitor = { + let mut drop_range_visitor = DropRangeVisitor { + consumed_places: <_>::default(), + borrowed_places: <_>::default(), + drop_ranges: vec![<_>::default()], + expr_count: 0, + }; + + // Run ExprUseVisitor to find where values are consumed. + ExprUseVisitor::new( + &mut drop_range_visitor, + &fcx.infcx, + def_id.expect_local(), + fcx.param_env, + &fcx.typeck_results.borrow(), + ) + .consume_body(body); + intravisit::walk_body(&mut drop_range_visitor, body); + + InteriorVisitor { + fcx, + types: FxIndexSet::default(), + region_scope_tree: fcx.tcx.region_scope_tree(def_id), + expr_count: 0, + kind, + prev_unresolved_span: None, + guard_bindings: <_>::default(), + guard_bindings_set: <_>::default(), + linted_values: <_>::default(), + drop_ranges: drop_range_visitor.drop_ranges.pop().unwrap(), + } }; intravisit::walk_body(&mut visitor, body); @@ -656,17 +664,37 @@ fn check_must_not_suspend_def( } /// This struct facilitates computing the ranges for which a place is uninitialized. -#[derive(Default)] struct DropRangeVisitor { consumed_places: HirIdSet, - drop_ranges: HirIdMap, + borrowed_places: HirIdSet, + drop_ranges: Vec>, expr_count: usize, } impl DropRangeVisitor { fn record_drop(&mut self, hir_id: HirId) { - debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - self.drop_ranges.insert(hir_id, DropRange { dropped_at: self.expr_count }); + let drop_ranges = self.drop_ranges.last_mut().unwrap(); + if self.borrowed_places.contains(&hir_id) { + debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); + } else if self.consumed_places.contains(&hir_id) { + debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); + drop_ranges.insert(hir_id, DropRange { dropped_at: self.expr_count }); + } + } + + fn push_drop_scope(&mut self) { + self.drop_ranges.push(<_>::default()); + } + + fn pop_and_merge_drop_scope(&mut self) { + let mut old_last = self.drop_ranges.pop().unwrap(); + let drop_ranges = self.drop_ranges.last_mut().unwrap(); + for (k, v) in old_last.drain() { + match drop_ranges.get(&k).cloned() { + Some(v2) => drop_ranges.insert(k, v.intersect(&v2)), + None => drop_ranges.insert(k, v), + }; + } } /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all @@ -685,6 +713,14 @@ impl DropRangeVisitor { } } +fn place_hir_id(place: &Place<'_>) -> Option { + match place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => None, + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), + } +} + impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor { fn consume( &mut self, @@ -693,14 +729,16 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor { ) { debug!("consume {:?}; diag_expr_id={:?}", place_with_id, diag_expr_id); self.consumed_places.insert(place_with_id.hir_id); + place_hir_id(&place_with_id.place).map(|place| self.consumed_places.insert(place)); } fn borrow( &mut self, - _place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, _diag_expr_id: hir::HirId, _bk: rustc_middle::ty::BorrowKind, ) { + place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); } fn mutate( @@ -726,17 +764,44 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor { NestedVisitorMap::None } - fn visit_expr(&mut self, expr: &Expr<'_>) { - intravisit::walk_expr(self, expr); + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + match expr.kind { + ExprKind::AssignOp(_, lhs, rhs) => { + // These operations are weird because their order of evaluation depends on whether + // the operator is overloaded. In a perfect world, we'd just ask the type checker + // whether this is a method call, but we also need to match the expression IDs + // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, + // so it runs both orders and picks the most conservative. We'll mirror that here. + let mut old_count = self.expr_count; + intravisit::walk_expr(self, lhs); + intravisit::walk_expr(self, rhs); + + self.push_drop_scope(); + std::mem::swap(&mut old_count, &mut self.expr_count); + intravisit::walk_expr(self, rhs); + intravisit::walk_expr(self, lhs); + + // We should have visited the same number of expressions in either order. + assert_eq!(old_count, self.expr_count); + + self.pop_and_merge_drop_scope(); + } + _ => intravisit::walk_expr(self, expr), + } self.expr_count += 1; + self.consume_expr(expr); + } - if self.consumed_places.contains(&expr.hir_id) { - self.consume_expr(expr); - } + fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) { + intravisit::walk_pat(self, pat); + + // Increment expr_count here to match what InteriorVisitor expects. + self.expr_count += 1; } } +#[derive(Clone)] struct DropRange { /// The post-order id of the point where this expression is dropped. /// @@ -745,7 +810,11 @@ struct DropRange { } impl DropRange { + fn intersect(&self, other: &Self) -> Self { + Self { dropped_at: self.dropped_at.max(other.dropped_at) } + } + fn contains(&self, id: usize) -> bool { - id >= self.dropped_at + id > self.dropped_at } } diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index 4dd36e7f0f062..210d9ff3f2d32 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -36,7 +36,7 @@ async fn non_send_temporary_in_match() { } async fn non_sync_with_method_call() { - + // FIXME: it would be nice for this to work let f: &mut std::fmt::Formatter = panic!(); if non_sync().fmt(f).unwrap() == () { fut().await; @@ -47,9 +47,8 @@ fn assert_send(_: impl Send) {} pub fn pass_assert() { assert_send(local_dropped_before_await()); - assert_send(non_send_temporary_in_match()); //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_method_call()); - + //~^ ERROR future cannot be sent between threads safely } From f246c0b116cdbbad570c23c5745aa01f6f3f64a0 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 25 Oct 2021 17:01:24 -0700 Subject: [PATCH 10/69] Attribute drop to parent expression of the consume point This is needed to handle cases like `[a, b.await, c]`. `ExprUseVisitor` considers `a` to be consumed when it is passed to the array, but the array is not quite live yet at that point. This means we were missing the `a` value across the await point. Attributing drops to the parent expression means we do not consider the value consumed until the consuming expression has finished. Issue #57478 --- .../src/check/generator_interior.rs | 70 +++++++++++++------ .../ui/async-await/unresolved_type_param.rs | 8 +++ .../async-await/unresolved_type_param.stderr | 26 ++++++- src/test/ui/lint/must_not_suspend/dedup.rs | 2 +- .../ui/lint/must_not_suspend/dedup.stderr | 12 ++-- 5 files changed, 87 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index baeb78139ac96..92dea92a0bcad 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -6,7 +6,7 @@ use crate::expr_use_visitor::{self, ExprUseVisitor}; use super::FnCtxt; -use hir::HirIdMap; +use hir::{HirIdMap, Node}; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -15,6 +15,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::hir_id::HirIdSet; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind}; +use rustc_middle::hir::map::Map; use rustc_middle::hir::place::{Place, PlaceBase}; use rustc_middle::middle::region::{self, YieldData}; use rustc_middle::ty::{self, Ty, TyCtxt}; @@ -225,6 +226,7 @@ pub fn resolve_interior<'a, 'tcx>( let mut visitor = { let mut drop_range_visitor = DropRangeVisitor { + hir: fcx.tcx.hir(), consumed_places: <_>::default(), borrowed_places: <_>::default(), drop_ranges: vec![<_>::default()], @@ -664,19 +666,28 @@ fn check_must_not_suspend_def( } /// This struct facilitates computing the ranges for which a place is uninitialized. -struct DropRangeVisitor { - consumed_places: HirIdSet, +struct DropRangeVisitor<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + consumed_places: HirIdMap, borrowed_places: HirIdSet, drop_ranges: Vec>, expr_count: usize, } -impl DropRangeVisitor { +impl DropRangeVisitor<'tcx> { + fn mark_consumed(&mut self, consumer: HirId, target: HirId) { + if !self.consumed_places.contains_key(&consumer) { + self.consumed_places.insert(consumer, <_>::default()); + } + self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); + } + fn record_drop(&mut self, hir_id: HirId) { let drop_ranges = self.drop_ranges.last_mut().unwrap(); if self.borrowed_places.contains(&hir_id) { debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); - } else if self.consumed_places.contains(&hir_id) { + } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); drop_ranges.insert(hir_id, DropRange { dropped_at: self.expr_count }); } @@ -700,15 +711,24 @@ impl DropRangeVisitor { /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all /// expressions. This method consumes a little deeper into the expression when needed. fn consume_expr(&mut self, expr: &hir::Expr<'_>) { - self.record_drop(expr.hir_id); - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) => { - self.record_drop(*hir_id); + debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); + let places = self + .consumed_places + .get(&expr.hir_id) + .map_or(vec![], |places| places.iter().cloned().collect()); + for place in places { + self.record_drop(place); + if let Some(Node::Expr(expr)) = self.hir.find(place) { + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + self.record_drop(*hir_id); + } + _ => (), + } } - _ => (), } } } @@ -721,15 +741,19 @@ fn place_hir_id(place: &Place<'_>) -> Option { } } -impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor { +impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor<'tcx> { fn consume( &mut self, place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, diag_expr_id: hir::HirId, ) { - debug!("consume {:?}; diag_expr_id={:?}", place_with_id, diag_expr_id); - self.consumed_places.insert(place_with_id.hir_id); - place_hir_id(&place_with_id.place).map(|place| self.consumed_places.insert(place)); + let parent = match self.hir.find_parent_node(place_with_id.hir_id) { + Some(parent) => parent, + None => place_with_id.hir_id, + }; + debug!("consume {:?}; diag_expr_id={:?}, using parent {:?}", place_with_id, diag_expr_id, parent); + self.mark_consumed(parent, place_with_id.hir_id); + place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); } fn borrow( @@ -757,7 +781,7 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor { } } -impl<'tcx> Visitor<'tcx> for DropRangeVisitor { +impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { type Map = intravisit::ErasedMap<'tcx>; fn nested_visit_map(&mut self) -> NestedVisitorMap { @@ -766,20 +790,20 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { match expr.kind { - ExprKind::AssignOp(_, lhs, rhs) => { + ExprKind::AssignOp(_op, lhs, rhs) => { // These operations are weird because their order of evaluation depends on whether // the operator is overloaded. In a perfect world, we'd just ask the type checker // whether this is a method call, but we also need to match the expression IDs // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, // so it runs both orders and picks the most conservative. We'll mirror that here. let mut old_count = self.expr_count; - intravisit::walk_expr(self, lhs); - intravisit::walk_expr(self, rhs); + self.visit_expr(lhs); + self.visit_expr(rhs); self.push_drop_scope(); std::mem::swap(&mut old_count, &mut self.expr_count); - intravisit::walk_expr(self, rhs); - intravisit::walk_expr(self, lhs); + self.visit_expr(rhs); + self.visit_expr(lhs); // We should have visited the same number of expressions in either order. assert_eq!(old_count, self.expr_count); diff --git a/src/test/ui/async-await/unresolved_type_param.rs b/src/test/ui/async-await/unresolved_type_param.rs index 79c043b701ddb..d313691b38857 100644 --- a/src/test/ui/async-await/unresolved_type_param.rs +++ b/src/test/ui/async-await/unresolved_type_param.rs @@ -8,8 +8,16 @@ async fn bar() -> () {} async fn foo() { bar().await; //~^ ERROR type inside `async fn` body must be known in this context + //~| ERROR type inside `async fn` body must be known in this context + //~| ERROR type inside `async fn` body must be known in this context //~| NOTE cannot infer type for type parameter `T` + //~| NOTE cannot infer type for type parameter `T` + //~| NOTE cannot infer type for type parameter `T` + //~| NOTE the type is part of the `async fn` body because of this `await` //~| NOTE the type is part of the `async fn` body because of this `await` + //~| NOTE the type is part of the `async fn` body because of this `await` + //~| NOTE in this expansion of desugaring of `await` + //~| NOTE in this expansion of desugaring of `await` //~| NOTE in this expansion of desugaring of `await` } fn main() {} diff --git a/src/test/ui/async-await/unresolved_type_param.stderr b/src/test/ui/async-await/unresolved_type_param.stderr index 853e53ed69df2..6a268bcda6297 100644 --- a/src/test/ui/async-await/unresolved_type_param.stderr +++ b/src/test/ui/async-await/unresolved_type_param.stderr @@ -10,6 +10,30 @@ note: the type is part of the `async fn` body because of this `await` LL | bar().await; | ^^^^^^ -error: aborting due to previous error +error[E0698]: type inside `async fn` body must be known in this context + --> $DIR/unresolved_type_param.rs:9:5 + | +LL | bar().await; + | ^^^ cannot infer type for type parameter `T` declared on the function `bar` + | +note: the type is part of the `async fn` body because of this `await` + --> $DIR/unresolved_type_param.rs:9:5 + | +LL | bar().await; + | ^^^^^^^^^^^ + +error[E0698]: type inside `async fn` body must be known in this context + --> $DIR/unresolved_type_param.rs:9:5 + | +LL | bar().await; + | ^^^ cannot infer type for type parameter `T` declared on the function `bar` + | +note: the type is part of the `async fn` body because of this `await` + --> $DIR/unresolved_type_param.rs:9:5 + | +LL | bar().await; + | ^^^^^^^^^^^ + +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0698`. diff --git a/src/test/ui/lint/must_not_suspend/dedup.rs b/src/test/ui/lint/must_not_suspend/dedup.rs index 040fff5a5a5a8..81a08579bb7bc 100644 --- a/src/test/ui/lint/must_not_suspend/dedup.rs +++ b/src/test/ui/lint/must_not_suspend/dedup.rs @@ -13,7 +13,7 @@ async fn wheeee(t: T) { } async fn yes() { - wheeee(No {}).await; //~ ERROR `No` held across + wheeee(&No {}).await; //~ ERROR `No` held across } fn main() { diff --git a/src/test/ui/lint/must_not_suspend/dedup.stderr b/src/test/ui/lint/must_not_suspend/dedup.stderr index bc1b611299a2b..d15137474527e 100644 --- a/src/test/ui/lint/must_not_suspend/dedup.stderr +++ b/src/test/ui/lint/must_not_suspend/dedup.stderr @@ -1,8 +1,8 @@ error: `No` held across a suspend point, but should not be - --> $DIR/dedup.rs:16:12 + --> $DIR/dedup.rs:16:13 | -LL | wheeee(No {}).await; - | ^^^^^ ------ the value is held across this suspend point +LL | wheeee(&No {}).await; + | --------^^^^^------- the value is held across this suspend point | note: the lint level is defined here --> $DIR/dedup.rs:3:9 @@ -10,10 +10,10 @@ note: the lint level is defined here LL | #![deny(must_not_suspend)] | ^^^^^^^^^^^^^^^^ help: consider using a block (`{ ... }`) to shrink the value's scope, ending before the suspend point - --> $DIR/dedup.rs:16:12 + --> $DIR/dedup.rs:16:13 | -LL | wheeee(No {}).await; - | ^^^^^ +LL | wheeee(&No {}).await; + | ^^^^^ error: aborting due to previous error From aa029d4bbe78fafbffdebb398a767941459d9d4e Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 27 Oct 2021 17:46:08 -0700 Subject: [PATCH 11/69] Support conditional drops This adds support for branching and merging control flow and uses this to correctly handle the case where a value is dropped in one branch of an if expression but not another. There are other cases we need to handle, which will come in follow up patches. Issue #57478 --- Cargo.lock | 1 + compiler/rustc_typeck/Cargo.toml | 1 + .../src/check/generator_interior.rs | 224 +++++++++++++++--- src/test/ui/generator/drop-if.rs | 22 ++ 4 files changed, 220 insertions(+), 28 deletions(-) create mode 100644 src/test/ui/generator/drop-if.rs diff --git a/Cargo.lock b/Cargo.lock index 529e17b158fc8..dfe3db5907a33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4383,6 +4383,7 @@ dependencies = [ name = "rustc_typeck" version = "0.0.0" dependencies = [ + "itertools 0.9.0", "rustc_arena", "rustc_ast", "rustc_attr", diff --git a/compiler/rustc_typeck/Cargo.toml b/compiler/rustc_typeck/Cargo.toml index 7e570e151c529..1da106d7002ef 100644 --- a/compiler/rustc_typeck/Cargo.toml +++ b/compiler/rustc_typeck/Cargo.toml @@ -8,6 +8,7 @@ test = false doctest = false [dependencies] +itertools = "0.9" rustc_arena = { path = "../rustc_arena" } tracing = "0.1" rustc_macros = { path = "../rustc_macros" } diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 92dea92a0bcad..6144cbbd8dd4b 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,10 +3,13 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. +use std::mem; + use crate::expr_use_visitor::{self, ExprUseVisitor}; use super::FnCtxt; use hir::{HirIdMap, Node}; +use itertools::Itertools; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -24,6 +27,9 @@ use rustc_span::Span; use smallvec::SmallVec; use tracing::debug; +#[cfg(test)] +mod tests; + struct InteriorVisitor<'a, 'tcx> { fcx: &'a FnCtxt<'a, 'tcx>, types: FxIndexSet>, @@ -80,7 +86,9 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { ); match self.drop_ranges.get(&hir_id) { - Some(range) if range.contains(yield_data.expr_and_pat_count) => { + Some(range) + if range.is_dropped_at(yield_data.expr_and_pat_count) => + { debug!("value is dropped at yield point; not recording"); return false; } @@ -229,7 +237,7 @@ pub fn resolve_interior<'a, 'tcx>( hir: fcx.tcx.hir(), consumed_places: <_>::default(), borrowed_places: <_>::default(), - drop_ranges: vec![<_>::default()], + drop_ranges: <_>::default(), expr_count: 0, }; @@ -254,7 +262,7 @@ pub fn resolve_interior<'a, 'tcx>( guard_bindings: <_>::default(), guard_bindings_set: <_>::default(), linted_values: <_>::default(), - drop_ranges: drop_range_visitor.drop_ranges.pop().unwrap(), + drop_ranges: drop_range_visitor.drop_ranges, } }; intravisit::walk_body(&mut visitor, body); @@ -671,7 +679,7 @@ struct DropRangeVisitor<'tcx> { /// Maps a HirId to a set of HirIds that are dropped by that node. consumed_places: HirIdMap, borrowed_places: HirIdSet, - drop_ranges: Vec>, + drop_ranges: HirIdMap, expr_count: usize, } @@ -684,28 +692,42 @@ impl DropRangeVisitor<'tcx> { } fn record_drop(&mut self, hir_id: HirId) { - let drop_ranges = self.drop_ranges.last_mut().unwrap(); + let drop_ranges = &mut self.drop_ranges; if self.borrowed_places.contains(&hir_id) { debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - drop_ranges.insert(hir_id, DropRange { dropped_at: self.expr_count }); + drop_ranges.insert(hir_id, DropRange::new(self.expr_count)); } } - fn push_drop_scope(&mut self) { - self.drop_ranges.push(<_>::default()); + fn swap_drop_ranges(&mut self, mut other: HirIdMap) -> HirIdMap { + mem::swap(&mut self.drop_ranges, &mut other); + other } - fn pop_and_merge_drop_scope(&mut self) { - let mut old_last = self.drop_ranges.pop().unwrap(); - let drop_ranges = self.drop_ranges.last_mut().unwrap(); - for (k, v) in old_last.drain() { - match drop_ranges.get(&k).cloned() { - Some(v2) => drop_ranges.insert(k, v.intersect(&v2)), - None => drop_ranges.insert(k, v), - }; - } + #[allow(dead_code)] + fn fork_drop_ranges(&self) -> HirIdMap { + self.drop_ranges.iter().map(|(k, v)| (*k, v.fork_at(self.expr_count))).collect() + } + + fn intersect_drop_ranges(&mut self, drops: HirIdMap) { + drops.into_iter().for_each(|(k, v)| match self.drop_ranges.get_mut(&k) { + Some(ranges) => *ranges = ranges.intersect(&v), + None => { + self.drop_ranges.insert(k, v); + } + }) + } + + #[allow(dead_code)] + fn merge_drop_ranges(&mut self, drops: HirIdMap) { + drops.into_iter().for_each(|(k, v)| { + if !self.drop_ranges.contains_key(&k) { + self.drop_ranges.insert(k, DropRange { events: vec![] }); + } + self.drop_ranges.get_mut(&k).unwrap().merge_with(&v, self.expr_count); + }); } /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all @@ -751,7 +773,10 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor<'tcx> { Some(parent) => parent, None => place_with_id.hir_id, }; - debug!("consume {:?}; diag_expr_id={:?}, using parent {:?}", place_with_id, diag_expr_id, parent); + debug!( + "consume {:?}; diag_expr_id={:?}, using parent {:?}", + place_with_id, diag_expr_id, parent + ); self.mark_consumed(parent, place_with_id.hir_id); place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); } @@ -800,7 +825,7 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.visit_expr(lhs); self.visit_expr(rhs); - self.push_drop_scope(); + let old_drops = self.swap_drop_ranges(<_>::default()); std::mem::swap(&mut old_count, &mut self.expr_count); self.visit_expr(rhs); self.visit_expr(lhs); @@ -808,7 +833,39 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { // We should have visited the same number of expressions in either order. assert_eq!(old_count, self.expr_count); - self.pop_and_merge_drop_scope(); + self.intersect_drop_ranges(old_drops); + } + ExprKind::If(test, if_true, if_false) => { + self.visit_expr(test); + + match if_false { + Some(if_false) => { + let mut true_ranges = self.fork_drop_ranges(); + let mut false_ranges = self.fork_drop_ranges(); + + true_ranges = self.swap_drop_ranges(true_ranges); + self.visit_expr(if_true); + true_ranges = self.swap_drop_ranges(true_ranges); + + false_ranges = self.swap_drop_ranges(false_ranges); + self.visit_expr(if_false); + false_ranges = self.swap_drop_ranges(false_ranges); + + self.merge_drop_ranges(true_ranges); + self.merge_drop_ranges(false_ranges); + } + None => { + let mut true_ranges = self.fork_drop_ranges(); + debug!("true branch drop range fork: {:?}", true_ranges); + true_ranges = self.swap_drop_ranges(true_ranges); + self.visit_expr(if_true); + true_ranges = self.swap_drop_ranges(true_ranges); + debug!("true branch computed drop_ranges: {:?}", true_ranges); + debug!("drop ranges before merging: {:?}", self.drop_ranges); + self.merge_drop_ranges(true_ranges); + debug!("drop ranges after merging: {:?}", self.drop_ranges); + } + } } _ => intravisit::walk_expr(self, expr), } @@ -825,20 +882,131 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } } -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq, Eq)] +enum Event { + Drop(usize), + Reinit(usize), +} + +impl Event { + fn location(&self) -> usize { + match *self { + Event::Drop(i) | Event::Reinit(i) => i, + } + } +} + +impl PartialOrd for Event { + fn partial_cmp(&self, other: &Self) -> Option { + self.location().partial_cmp(&other.location()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] struct DropRange { - /// The post-order id of the point where this expression is dropped. - /// - /// We can consider the value dropped at any post-order id greater than dropped_at. - dropped_at: usize, + events: Vec, } impl DropRange { + fn new(begin: usize) -> Self { + Self { events: vec![Event::Drop(begin)] } + } + fn intersect(&self, other: &Self) -> Self { - Self { dropped_at: self.dropped_at.max(other.dropped_at) } + let mut events = vec![]; + self.events + .iter() + .merge_join_by(other.events.iter(), |a, b| a.partial_cmp(b).unwrap()) + .fold((false, false), |(left, right), event| match event { + itertools::EitherOrBoth::Both(_, _) => todo!(), + itertools::EitherOrBoth::Left(e) => match e { + Event::Drop(i) => { + if !left && right { + events.push(Event::Drop(*i)); + } + (true, right) + } + Event::Reinit(i) => { + if left && !right { + events.push(Event::Reinit(*i)); + } + (false, right) + } + }, + itertools::EitherOrBoth::Right(e) => match e { + Event::Drop(i) => { + if left && !right { + events.push(Event::Drop(*i)); + } + (left, true) + } + Event::Reinit(i) => { + if !left && right { + events.push(Event::Reinit(*i)); + } + (left, false) + } + }, + }); + Self { events } + } + + fn is_dropped_at(&self, id: usize) -> bool { + match self.events.iter().try_fold(false, |is_dropped, event| { + if event.location() < id { + Ok(match event { + Event::Drop(_) => true, + Event::Reinit(_) => false, + }) + } else { + Err(is_dropped) + } + }) { + Ok(is_dropped) | Err(is_dropped) => is_dropped, + } + } + + #[allow(dead_code)] + fn drop(&mut self, location: usize) { + self.events.push(Event::Drop(location)) + } + + #[allow(dead_code)] + fn reinit(&mut self, location: usize) { + self.events.push(Event::Reinit(location)); + } + + /// Merges another range with this one. Meant to be used at control flow join points. + /// + /// After merging, the value will be dead at the end of the range only if it was dead + /// at the end of both self and other. + /// + /// Assumes that all locations in each range are less than joinpoint + #[allow(dead_code)] + fn merge_with(&mut self, other: &DropRange, join_point: usize) { + let mut events: Vec<_> = + self.events.iter().merge(other.events.iter()).dedup().cloned().collect(); + + events.push(if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { + Event::Drop(join_point) + } else { + Event::Reinit(join_point) + }); + + self.events = events; } - fn contains(&self, id: usize) -> bool { - id > self.dropped_at + /// Creates a new DropRange from this one at the split point. + /// + /// Used to model branching control flow. + #[allow(dead_code)] + fn fork_at(&self, split_point: usize) -> Self { + Self { + events: vec![if self.is_dropped_at(split_point) { + Event::Drop(split_point) + } else { + Event::Reinit(split_point) + }], + } } } diff --git a/src/test/ui/generator/drop-if.rs b/src/test/ui/generator/drop-if.rs new file mode 100644 index 0000000000000..40f01f78662a3 --- /dev/null +++ b/src/test/ui/generator/drop-if.rs @@ -0,0 +1,22 @@ +// build-pass + +// This test case is reduced from src/test/ui/drop/dynamic-drop-async.rs + +#![feature(generators)] + +struct Ptr; +impl<'a> Drop for Ptr { + fn drop(&mut self) { + } +} + +fn main() { + let arg = true; + let _ = || { + let arr = [Ptr]; + if arg { + drop(arr); + } + yield + }; +} From 96117701f94a2c08235a87fce9d362ca26997017 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 3 Nov 2021 16:28:07 -0700 Subject: [PATCH 12/69] Support reinitialization of variables --- .../src/check/generator_interior.rs | 45 ++++++++--- src/test/ui/generator/drop-control-flow.rs | 81 +++++++++++++++++++ src/test/ui/generator/drop-if.rs | 22 ----- 3 files changed, 116 insertions(+), 32 deletions(-) create mode 100644 src/test/ui/generator/drop-control-flow.rs delete mode 100644 src/test/ui/generator/drop-if.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 6144cbbd8dd4b..65644a54c4fa1 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -691,13 +691,20 @@ impl DropRangeVisitor<'tcx> { self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); } + fn drop_range(&mut self, hir_id: &HirId) -> &mut DropRange { + if !self.drop_ranges.contains_key(hir_id) { + self.drop_ranges.insert(*hir_id, DropRange::empty()); + } + self.drop_ranges.get_mut(hir_id).unwrap() + } + fn record_drop(&mut self, hir_id: HirId) { - let drop_ranges = &mut self.drop_ranges; if self.borrowed_places.contains(&hir_id) { debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - drop_ranges.insert(hir_id, DropRange::new(self.expr_count)); + let count = self.expr_count; + self.drop_range(&hir_id).drop(count); } } @@ -706,7 +713,6 @@ impl DropRangeVisitor<'tcx> { other } - #[allow(dead_code)] fn fork_drop_ranges(&self) -> HirIdMap { self.drop_ranges.iter().map(|(k, v)| (*k, v.fork_at(self.expr_count))).collect() } @@ -720,7 +726,6 @@ impl DropRangeVisitor<'tcx> { }) } - #[allow(dead_code)] fn merge_drop_ranges(&mut self, drops: HirIdMap) { drops.into_iter().for_each(|(k, v)| { if !self.drop_ranges.contains_key(&k) { @@ -753,6 +758,20 @@ impl DropRangeVisitor<'tcx> { } } } + + fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { + if let ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) = expr.kind + { + let location = self.expr_count; + debug!("reinitializing {:?} at {}", hir_id, location); + self.drop_range(hir_id).reinit(location) + } else { + warn!("reinitializing {:?} is not supported", expr); + } + } } fn place_hir_id(place: &Place<'_>) -> Option { @@ -814,6 +833,7 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + let mut reinit = None; match expr.kind { ExprKind::AssignOp(_op, lhs, rhs) => { // These operations are weird because their order of evaluation depends on whether @@ -867,11 +887,20 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } } } + ExprKind::Assign(lhs, rhs, _) => { + self.visit_expr(lhs); + self.visit_expr(rhs); + + reinit = Some(lhs); + } _ => intravisit::walk_expr(self, expr), } self.expr_count += 1; self.consume_expr(expr); + if let Some(expr) = reinit { + self.reinit_expr(expr); + } } fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) { @@ -908,8 +937,8 @@ struct DropRange { } impl DropRange { - fn new(begin: usize) -> Self { - Self { events: vec![Event::Drop(begin)] } + fn empty() -> Self { + Self { events: vec![] } } fn intersect(&self, other: &Self) -> Self { @@ -966,12 +995,10 @@ impl DropRange { } } - #[allow(dead_code)] fn drop(&mut self, location: usize) { self.events.push(Event::Drop(location)) } - #[allow(dead_code)] fn reinit(&mut self, location: usize) { self.events.push(Event::Reinit(location)); } @@ -982,7 +1009,6 @@ impl DropRange { /// at the end of both self and other. /// /// Assumes that all locations in each range are less than joinpoint - #[allow(dead_code)] fn merge_with(&mut self, other: &DropRange, join_point: usize) { let mut events: Vec<_> = self.events.iter().merge(other.events.iter()).dedup().cloned().collect(); @@ -999,7 +1025,6 @@ impl DropRange { /// Creates a new DropRange from this one at the split point. /// /// Used to model branching control flow. - #[allow(dead_code)] fn fork_at(&self, split_point: usize) -> Self { Self { events: vec![if self.is_dropped_at(split_point) { diff --git a/src/test/ui/generator/drop-control-flow.rs b/src/test/ui/generator/drop-control-flow.rs new file mode 100644 index 0000000000000..b180a61b10410 --- /dev/null +++ b/src/test/ui/generator/drop-control-flow.rs @@ -0,0 +1,81 @@ +// build-pass + +// A test to ensure generators capture values that were conditionally dropped, +// and also that values that are dropped along all paths to a yield do not get +// included in the generator type. + +#![feature(generators, negative_impls)] + +#![allow(unused_assignments, dead_code)] + +struct Ptr; +impl<'a> Drop for Ptr { + fn drop(&mut self) {} +} + +struct NonSend {} +impl !Send for NonSend {} + +fn assert_send(_: T) {} + +// This test case is reduced from src/test/ui/drop/dynamic-drop-async.rs +fn one_armed_if(arg: bool) { + let _ = || { + let arr = [Ptr]; + if arg { + drop(arr); + } + yield; + }; +} + +fn two_armed_if(arg: bool) { + assert_send(|| { + let arr = [Ptr]; + if arg { + drop(arr); + } else { + drop(arr); + } + yield; + }) +} + +fn if_let(arg: Option) { + let _ = || { + let arr = [Ptr]; + if let Some(_) = arg { + drop(arr); + } + yield; + }; +} + +fn reinit() { + let _ = || { + let mut arr = [Ptr]; + drop(arr); + arr = [Ptr]; + yield; + }; +} + +fn loop_uninit() { + let _ = || { + let mut arr = [Ptr]; + let mut count = 0; + drop(arr); + while count < 3 { + yield; + arr = [Ptr]; + count += 1; + } + }; +} + +fn main() { + one_armed_if(true); + if_let(Some(41)); + reinit(); + // loop_uninit(); +} diff --git a/src/test/ui/generator/drop-if.rs b/src/test/ui/generator/drop-if.rs deleted file mode 100644 index 40f01f78662a3..0000000000000 --- a/src/test/ui/generator/drop-if.rs +++ /dev/null @@ -1,22 +0,0 @@ -// build-pass - -// This test case is reduced from src/test/ui/drop/dynamic-drop-async.rs - -#![feature(generators)] - -struct Ptr; -impl<'a> Drop for Ptr { - fn drop(&mut self) { - } -} - -fn main() { - let arg = true; - let _ = || { - let arr = [Ptr]; - if arg { - drop(arr); - } - yield - }; -} From 298ca2f6799201152e2e75a781a7aabb29424aea Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 4 Nov 2021 10:56:07 -0700 Subject: [PATCH 13/69] Basic loop support --- .../src/check/generator_interior.rs | 36 +++++++++++++++---- src/test/ui/generator/drop-control-flow.rs | 2 +- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 65644a54c4fa1..d7ad84684d1f1 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -726,15 +726,19 @@ impl DropRangeVisitor<'tcx> { }) } - fn merge_drop_ranges(&mut self, drops: HirIdMap) { + fn merge_drop_ranges_at(&mut self, drops: HirIdMap, join_point: usize) { drops.into_iter().for_each(|(k, v)| { if !self.drop_ranges.contains_key(&k) { self.drop_ranges.insert(k, DropRange { events: vec![] }); } - self.drop_ranges.get_mut(&k).unwrap().merge_with(&v, self.expr_count); + self.drop_ranges.get_mut(&k).unwrap().merge_with(&v, join_point); }); } + fn merge_drop_ranges(&mut self, drops: HirIdMap) { + self.merge_drop_ranges_at(drops, self.expr_count); + } + /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all /// expressions. This method consumes a little deeper into the expression when needed. fn consume_expr(&mut self, expr: &hir::Expr<'_>) { @@ -893,6 +897,17 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } + ExprKind::Loop(body, ..) => { + let body_drop_ranges = self.fork_drop_ranges(); + let old_drop_ranges = self.swap_drop_ranges(body_drop_ranges); + + let join_point = self.expr_count; + + self.visit_block(body); + + let body_drop_ranges = self.swap_drop_ranges(old_drop_ranges); + self.merge_drop_ranges_at(body_drop_ranges, join_point); + } _ => intravisit::walk_expr(self, expr), } @@ -1007,11 +1022,20 @@ impl DropRange { /// /// After merging, the value will be dead at the end of the range only if it was dead /// at the end of both self and other. - /// - /// Assumes that all locations in each range are less than joinpoint fn merge_with(&mut self, other: &DropRange, join_point: usize) { - let mut events: Vec<_> = - self.events.iter().merge(other.events.iter()).dedup().cloned().collect(); + let join_event = if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { + Event::Drop(join_point) + } else { + Event::Reinit(join_point) + }; + let mut events: Vec<_> = self + .events + .iter() + .merge([join_event].iter()) + .merge(other.events.iter()) + .dedup() + .cloned() + .collect(); events.push(if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { Event::Drop(join_point) diff --git a/src/test/ui/generator/drop-control-flow.rs b/src/test/ui/generator/drop-control-flow.rs index b180a61b10410..6587e54df6083 100644 --- a/src/test/ui/generator/drop-control-flow.rs +++ b/src/test/ui/generator/drop-control-flow.rs @@ -77,5 +77,5 @@ fn main() { one_armed_if(true); if_let(Some(41)); reinit(); - // loop_uninit(); + loop_uninit(); } From 457415294c57dff4b07cc06165eb284d9a14a24a Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 4 Nov 2021 16:38:47 -0700 Subject: [PATCH 14/69] Handle more cases with conditionally initialized/dropped values --- .../src/check/generator_interior.rs | 48 ++++++++++++++++++- src/test/ui/generator/drop-control-flow.rs | 44 ++++++++++++++++- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index d7ad84684d1f1..e7794b199e714 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -871,7 +871,8 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.visit_expr(if_true); true_ranges = self.swap_drop_ranges(true_ranges); - false_ranges = self.swap_drop_ranges(false_ranges); + false_ranges = + self.swap_drop_ranges(trim_drop_ranges(&false_ranges, self.expr_count)); self.visit_expr(if_false); false_ranges = self.swap_drop_ranges(false_ranges); @@ -908,6 +909,31 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { let body_drop_ranges = self.swap_drop_ranges(old_drop_ranges); self.merge_drop_ranges_at(body_drop_ranges, join_point); } + ExprKind::Match(scrutinee, arms, ..) => { + self.visit_expr(scrutinee); + + let forked_ranges = self.fork_drop_ranges(); + let arm_drops = arms + .iter() + .map(|Arm { hir_id, pat, body, guard, .. }| { + debug!("match arm {:?} starts at {}", hir_id, self.expr_count); + let old_ranges = self + .swap_drop_ranges(trim_drop_ranges(&forked_ranges, self.expr_count)); + self.visit_pat(pat); + match guard { + Some(Guard::If(expr)) => self.visit_expr(expr), + Some(Guard::IfLet(pat, expr)) => { + self.visit_pat(pat); + self.visit_expr(expr); + } + None => (), + } + self.visit_expr(body); + self.swap_drop_ranges(old_ranges) + }) + .collect::>(); + arm_drops.into_iter().for_each(|drops| self.merge_drop_ranges(drops)); + } _ => intravisit::walk_expr(self, expr), } @@ -926,6 +952,10 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } } +fn trim_drop_ranges(drop_ranges: &HirIdMap, trim_from: usize) -> HirIdMap { + drop_ranges.iter().map(|(k, v)| (*k, v.trimmed(trim_from))).collect() +} + #[derive(Clone, Debug, PartialEq, Eq)] enum Event { Drop(usize), @@ -1058,4 +1088,20 @@ impl DropRange { }], } } + + fn trimmed(&self, trim_from: usize) -> Self { + let start = if self.is_dropped_at(trim_from) { + Event::Drop(trim_from) + } else { + Event::Reinit(trim_from) + }; + + Self { + events: [start] + .iter() + .chain(self.events.iter().skip_while(|event| event.location() <= trim_from)) + .cloned() + .collect(), + } + } } diff --git a/src/test/ui/generator/drop-control-flow.rs b/src/test/ui/generator/drop-control-flow.rs index 6587e54df6083..6319a29f5b7d0 100644 --- a/src/test/ui/generator/drop-control-flow.rs +++ b/src/test/ui/generator/drop-control-flow.rs @@ -5,7 +5,6 @@ // included in the generator type. #![feature(generators, negative_impls)] - #![allow(unused_assignments, dead_code)] struct Ptr; @@ -13,7 +12,7 @@ impl<'a> Drop for Ptr { fn drop(&mut self) {} } -struct NonSend {} +struct NonSend; impl !Send for NonSend {} fn assert_send(_: T) {} @@ -51,6 +50,29 @@ fn if_let(arg: Option) { }; } +fn init_in_if(arg: bool) { + assert_send(|| { + let mut x = NonSend; + drop(x); + if arg { + x = NonSend; + } else { + yield; + } + }) +} + +fn init_in_match_arm(arg: Option) { + assert_send(|| { + let mut x = NonSend; + drop(x); + match arg { + Some(_) => x = NonSend, + None => yield, + } + }) +} + fn reinit() { let _ = || { let mut arr = [Ptr]; @@ -73,9 +95,27 @@ fn loop_uninit() { }; } +fn nested_loop() { + let _ = || { + let mut arr = [Ptr]; + let mut count = 0; + drop(arr); + while count < 3 { + for _ in 0..3 { + yield; + } + arr = [Ptr]; + count += 1; + } + }; +} + fn main() { one_armed_if(true); if_let(Some(41)); + init_in_if(true); + init_in_match_arm(Some(41)); reinit(); loop_uninit(); + nested_loop(); } From ba7d12731eb23154ea42feecec172bbef73fd865 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 5 Nov 2021 15:10:33 -0700 Subject: [PATCH 15/69] More tracing and tests --- .../src/check/generator_interior.rs | 32 +++++++++++-------- .../src/check/generator_interior/tests.rs | 14 ++++++++ 2 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 compiler/rustc_typeck/src/check/generator_interior/tests.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index e7794b199e714..3097729301840 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -773,7 +773,7 @@ impl DropRangeVisitor<'tcx> { debug!("reinitializing {:?} at {}", hir_id, location); self.drop_range(hir_id).reinit(location) } else { - warn!("reinitializing {:?} is not supported", expr); + debug!("reinitializing {:?} is not supported", expr); } } } @@ -899,6 +899,7 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } ExprKind::Loop(body, ..) => { + // FIXME: we probably need to iterate this to a fixpoint. let body_drop_ranges = self.fork_drop_ranges(); let old_drop_ranges = self.swap_drop_ranges(body_drop_ranges); @@ -1026,8 +1027,8 @@ impl DropRange { } fn is_dropped_at(&self, id: usize) -> bool { - match self.events.iter().try_fold(false, |is_dropped, event| { - if event.location() < id { + let dropped = match self.events.iter().try_fold(false, |is_dropped, event| { + if event.location() <= id { Ok(match event { Event::Drop(_) => true, Event::Reinit(_) => false, @@ -1037,7 +1038,9 @@ impl DropRange { } }) { Ok(is_dropped) | Err(is_dropped) => is_dropped, - } + }; + trace!("is_dropped_at({}): events = {:?}, dropped = {}", id, self.events, dropped); + dropped } fn drop(&mut self, location: usize) { @@ -1052,13 +1055,14 @@ impl DropRange { /// /// After merging, the value will be dead at the end of the range only if it was dead /// at the end of both self and other. + #[tracing::instrument] fn merge_with(&mut self, other: &DropRange, join_point: usize) { let join_event = if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { Event::Drop(join_point) } else { Event::Reinit(join_point) }; - let mut events: Vec<_> = self + let events: Vec<_> = self .events .iter() .merge([join_event].iter()) @@ -1067,11 +1071,7 @@ impl DropRange { .cloned() .collect(); - events.push(if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { - Event::Drop(join_point) - } else { - Event::Reinit(join_point) - }); + trace!("events after merging: {:?}", events); self.events = events; } @@ -1080,13 +1080,15 @@ impl DropRange { /// /// Used to model branching control flow. fn fork_at(&self, split_point: usize) -> Self { - Self { + let result = Self { events: vec![if self.is_dropped_at(split_point) { Event::Drop(split_point) } else { Event::Reinit(split_point) }], - } + }; + trace!("forking at {}: {:?}; result = {:?}", split_point, self.events, result); + result } fn trimmed(&self, trim_from: usize) -> Self { @@ -1096,12 +1098,14 @@ impl DropRange { Event::Reinit(trim_from) }; - Self { + let result = Self { events: [start] .iter() .chain(self.events.iter().skip_while(|event| event.location() <= trim_from)) .cloned() .collect(), - } + }; + trace!("trimmed {:?} at {}, got {:?}", self, trim_from, result); + result } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/tests.rs b/compiler/rustc_typeck/src/check/generator_interior/tests.rs new file mode 100644 index 0000000000000..8f973bb94895a --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/tests.rs @@ -0,0 +1,14 @@ +use super::DropRange; + +#[test] +fn drop_range_uses_last_event() { + let mut range = DropRange::empty(); + range.drop(10); + range.reinit(10); + assert!(!range.is_dropped_at(10)); + + let mut range = DropRange::empty(); + range.reinit(10); + range.drop(10); + assert!(range.is_dropped_at(10)); +} From ff0e8f4ba2e3133a167b138ba1ed69d1708fff16 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 10 Nov 2021 17:32:04 -0800 Subject: [PATCH 16/69] Revamped DropRange data structure Not currently working. Need to flow drop information. --- Cargo.lock | 2 +- compiler/rustc_typeck/Cargo.toml | 2 +- .../src/check/generator_interior.rs | 388 +++++------------- .../check/generator_interior/drop_ranges.rs | 173 ++++++++ 4 files changed, 273 insertions(+), 292 deletions(-) create mode 100644 compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs diff --git a/Cargo.lock b/Cargo.lock index dfe3db5907a33..b46696dde7cf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4383,7 +4383,6 @@ dependencies = [ name = "rustc_typeck" version = "0.0.0" dependencies = [ - "itertools 0.9.0", "rustc_arena", "rustc_ast", "rustc_attr", @@ -4396,6 +4395,7 @@ dependencies = [ "rustc_lint", "rustc_macros", "rustc_middle", + "rustc_serialize", "rustc_session", "rustc_span", "rustc_target", diff --git a/compiler/rustc_typeck/Cargo.toml b/compiler/rustc_typeck/Cargo.toml index 1da106d7002ef..3279c331ade18 100644 --- a/compiler/rustc_typeck/Cargo.toml +++ b/compiler/rustc_typeck/Cargo.toml @@ -8,7 +8,6 @@ test = false doctest = false [dependencies] -itertools = "0.9" rustc_arena = { path = "../rustc_arena" } tracing = "0.1" rustc_macros = { path = "../rustc_macros" } @@ -28,3 +27,4 @@ rustc_infer = { path = "../rustc_infer" } rustc_trait_selection = { path = "../rustc_trait_selection" } rustc_ty_utils = { path = "../rustc_ty_utils" } rustc_lint = { path = "../rustc_lint" } +rustc_serialize = { path = "../rustc_serialize" } diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 3097729301840..33533ab6189f0 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,13 +3,12 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. -use std::mem; - use crate::expr_use_visitor::{self, ExprUseVisitor}; +use self::drop_ranges::DropRanges; + use super::FnCtxt; use hir::{HirIdMap, Node}; -use itertools::Itertools; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -30,6 +29,8 @@ use tracing::debug; #[cfg(test)] mod tests; +mod drop_ranges; + struct InteriorVisitor<'a, 'tcx> { fcx: &'a FnCtxt<'a, 'tcx>, types: FxIndexSet>, @@ -45,7 +46,7 @@ struct InteriorVisitor<'a, 'tcx> { guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>, guard_bindings_set: HirIdSet, linted_values: HirIdSet, - drop_ranges: HirIdMap, + drop_ranges: DropRanges, } impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { @@ -85,14 +86,10 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { yield_data.expr_and_pat_count, self.expr_count, source_span ); - match self.drop_ranges.get(&hir_id) { - Some(range) - if range.is_dropped_at(yield_data.expr_and_pat_count) => - { - debug!("value is dropped at yield point; not recording"); - return false; - } - _ => (), + if self.drop_ranges.is_dropped_at(hir_id, yield_data.expr_and_pat_count) + { + debug!("value is dropped at yield point; not recording"); + return false; } // If it is a borrowing happening in the guard, @@ -233,25 +230,27 @@ pub fn resolve_interior<'a, 'tcx>( let body = fcx.tcx.hir().body(body_id); let mut visitor = { - let mut drop_range_visitor = DropRangeVisitor { + let mut expr_use_visitor = ExprUseDelegate { hir: fcx.tcx.hir(), consumed_places: <_>::default(), borrowed_places: <_>::default(), - drop_ranges: <_>::default(), - expr_count: 0, }; // Run ExprUseVisitor to find where values are consumed. ExprUseVisitor::new( - &mut drop_range_visitor, + &mut expr_use_visitor, &fcx.infcx, def_id.expect_local(), fcx.param_env, &fcx.typeck_results.borrow(), ) .consume_body(body); + + let mut drop_range_visitor = DropRangeVisitor::from(expr_use_visitor); intravisit::walk_body(&mut drop_range_visitor, body); + drop_range_visitor.drop_ranges.propagate_to_fixpoint(); + InteriorVisitor { fcx, types: FxIndexSet::default(), @@ -673,29 +672,46 @@ fn check_must_not_suspend_def( false } -/// This struct facilitates computing the ranges for which a place is uninitialized. -struct DropRangeVisitor<'tcx> { +struct ExprUseDelegate<'tcx> { hir: Map<'tcx>, /// Maps a HirId to a set of HirIds that are dropped by that node. consumed_places: HirIdMap, borrowed_places: HirIdSet, - drop_ranges: HirIdMap, - expr_count: usize, } -impl DropRangeVisitor<'tcx> { +impl<'tcx> ExprUseDelegate<'tcx> { fn mark_consumed(&mut self, consumer: HirId, target: HirId) { if !self.consumed_places.contains_key(&consumer) { self.consumed_places.insert(consumer, <_>::default()); } self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); } +} - fn drop_range(&mut self, hir_id: &HirId) -> &mut DropRange { - if !self.drop_ranges.contains_key(hir_id) { - self.drop_ranges.insert(*hir_id, DropRange::empty()); +/// This struct facilitates computing the ranges for which a place is uninitialized. +struct DropRangeVisitor<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + consumed_places: HirIdMap, + borrowed_places: HirIdSet, + drop_ranges: DropRanges, + expr_count: usize, +} + +impl<'tcx> DropRangeVisitor<'tcx> { + fn from(uses: ExprUseDelegate<'tcx>) -> Self { + debug!("consumed_places: {:?}", uses.consumed_places); + let drop_ranges = DropRanges::new( + uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), + &uses.hir, + ); + Self { + hir: uses.hir, + consumed_places: uses.consumed_places, + borrowed_places: uses.borrowed_places, + drop_ranges, + expr_count: 0, } - self.drop_ranges.get_mut(hir_id).unwrap() } fn record_drop(&mut self, hir_id: HirId) { @@ -704,41 +720,10 @@ impl DropRangeVisitor<'tcx> { } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); let count = self.expr_count; - self.drop_range(&hir_id).drop(count); + self.drop_ranges.drop_at(hir_id, count); } } - fn swap_drop_ranges(&mut self, mut other: HirIdMap) -> HirIdMap { - mem::swap(&mut self.drop_ranges, &mut other); - other - } - - fn fork_drop_ranges(&self) -> HirIdMap { - self.drop_ranges.iter().map(|(k, v)| (*k, v.fork_at(self.expr_count))).collect() - } - - fn intersect_drop_ranges(&mut self, drops: HirIdMap) { - drops.into_iter().for_each(|(k, v)| match self.drop_ranges.get_mut(&k) { - Some(ranges) => *ranges = ranges.intersect(&v), - None => { - self.drop_ranges.insert(k, v); - } - }) - } - - fn merge_drop_ranges_at(&mut self, drops: HirIdMap, join_point: usize) { - drops.into_iter().for_each(|(k, v)| { - if !self.drop_ranges.contains_key(&k) { - self.drop_ranges.insert(k, DropRange { events: vec![] }); - } - self.drop_ranges.get_mut(&k).unwrap().merge_with(&v, join_point); - }); - } - - fn merge_drop_ranges(&mut self, drops: HirIdMap) { - self.merge_drop_ranges_at(drops, self.expr_count); - } - /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all /// expressions. This method consumes a little deeper into the expression when needed. fn consume_expr(&mut self, expr: &hir::Expr<'_>) { @@ -748,18 +733,7 @@ impl DropRangeVisitor<'tcx> { .get(&expr.hir_id) .map_or(vec![], |places| places.iter().cloned().collect()); for place in places { - self.record_drop(place); - if let Some(Node::Expr(expr)) = self.hir.find(place) { - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) => { - self.record_drop(*hir_id); - } - _ => (), - } - } + for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); } } @@ -771,13 +745,28 @@ impl DropRangeVisitor<'tcx> { { let location = self.expr_count; debug!("reinitializing {:?} at {}", hir_id, location); - self.drop_range(hir_id).reinit(location) + self.drop_ranges.reinit_at(*hir_id, location); } else { debug!("reinitializing {:?} is not supported", expr); } } } +fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(HirId)) { + f(place); + if let Some(Node::Expr(expr)) = node { + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + f(*hir_id); + } + _ => (), + } + } +} + fn place_hir_id(place: &Place<'_>) -> Option { match place.base { PlaceBase::Rvalue | PlaceBase::StaticItem => None, @@ -786,7 +775,7 @@ fn place_hir_id(place: &Place<'_>) -> Option { } } -impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor<'tcx> { +impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { fn consume( &mut self, place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, @@ -839,58 +828,41 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut reinit = None; match expr.kind { - ExprKind::AssignOp(_op, lhs, rhs) => { - // These operations are weird because their order of evaluation depends on whether - // the operator is overloaded. In a perfect world, we'd just ask the type checker - // whether this is a method call, but we also need to match the expression IDs - // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, - // so it runs both orders and picks the most conservative. We'll mirror that here. - let mut old_count = self.expr_count; - self.visit_expr(lhs); - self.visit_expr(rhs); - - let old_drops = self.swap_drop_ranges(<_>::default()); - std::mem::swap(&mut old_count, &mut self.expr_count); - self.visit_expr(rhs); - self.visit_expr(lhs); - - // We should have visited the same number of expressions in either order. - assert_eq!(old_count, self.expr_count); - - self.intersect_drop_ranges(old_drops); - } + // ExprKind::AssignOp(_op, lhs, rhs) => { + // // These operations are weird because their order of evaluation depends on whether + // // the operator is overloaded. In a perfect world, we'd just ask the type checker + // // whether this is a method call, but we also need to match the expression IDs + // // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, + // // so it runs both orders and picks the most conservative. We'll mirror that here. + // let mut old_count = self.expr_count; + // self.visit_expr(lhs); + // self.visit_expr(rhs); + + // let old_drops = self.swap_drop_ranges(<_>::default()); + // std::mem::swap(&mut old_count, &mut self.expr_count); + // self.visit_expr(rhs); + // self.visit_expr(lhs); + + // // We should have visited the same number of expressions in either order. + // assert_eq!(old_count, self.expr_count); + + // self.intersect_drop_ranges(old_drops); + // } ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); - match if_false { - Some(if_false) => { - let mut true_ranges = self.fork_drop_ranges(); - let mut false_ranges = self.fork_drop_ranges(); + let fork = self.expr_count - 1; - true_ranges = self.swap_drop_ranges(true_ranges); - self.visit_expr(if_true); - true_ranges = self.swap_drop_ranges(true_ranges); + self.drop_ranges.add_control_edge(fork, self.expr_count); + self.visit_expr(if_true); + let true_end = self.expr_count - 1; - false_ranges = - self.swap_drop_ranges(trim_drop_ranges(&false_ranges, self.expr_count)); - self.visit_expr(if_false); - false_ranges = self.swap_drop_ranges(false_ranges); - - self.merge_drop_ranges(true_ranges); - self.merge_drop_ranges(false_ranges); - } - None => { - let mut true_ranges = self.fork_drop_ranges(); - debug!("true branch drop range fork: {:?}", true_ranges); - true_ranges = self.swap_drop_ranges(true_ranges); - self.visit_expr(if_true); - true_ranges = self.swap_drop_ranges(true_ranges); - debug!("true branch computed drop_ranges: {:?}", true_ranges); - debug!("drop ranges before merging: {:?}", self.drop_ranges); - self.merge_drop_ranges(true_ranges); - debug!("drop ranges after merging: {:?}", self.drop_ranges); - } + if let Some(if_false) = if_false { + self.drop_ranges.add_control_edge(fork, self.expr_count); + self.visit_expr(if_false); } + + self.drop_ranges.add_control_edge(true_end, self.expr_count); } ExprKind::Assign(lhs, rhs, _) => { self.visit_expr(lhs); @@ -899,27 +871,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } ExprKind::Loop(body, ..) => { - // FIXME: we probably need to iterate this to a fixpoint. - let body_drop_ranges = self.fork_drop_ranges(); - let old_drop_ranges = self.swap_drop_ranges(body_drop_ranges); - - let join_point = self.expr_count; - + let loop_begin = self.expr_count; self.visit_block(body); - - let body_drop_ranges = self.swap_drop_ranges(old_drop_ranges); - self.merge_drop_ranges_at(body_drop_ranges, join_point); + self.drop_ranges.add_control_edge(self.expr_count - 1, loop_begin); } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); - let forked_ranges = self.fork_drop_ranges(); + let fork = self.expr_count - 1; let arm_drops = arms .iter() - .map(|Arm { hir_id, pat, body, guard, .. }| { - debug!("match arm {:?} starts at {}", hir_id, self.expr_count); - let old_ranges = self - .swap_drop_ranges(trim_drop_ranges(&forked_ranges, self.expr_count)); + .map(|Arm { pat, body, guard, .. }| { + self.drop_ranges.add_control_edge(fork, self.expr_count); self.visit_pat(pat); match guard { Some(Guard::If(expr)) => self.visit_expr(expr), @@ -930,10 +893,12 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { None => (), } self.visit_expr(body); - self.swap_drop_ranges(old_ranges) + self.expr_count - 1 }) .collect::>(); - arm_drops.into_iter().for_each(|drops| self.merge_drop_ranges(drops)); + arm_drops.into_iter().for_each(|arm_end| { + self.drop_ranges.add_control_edge(arm_end, self.expr_count) + }); } _ => intravisit::walk_expr(self, expr), } @@ -952,160 +917,3 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.expr_count += 1; } } - -fn trim_drop_ranges(drop_ranges: &HirIdMap, trim_from: usize) -> HirIdMap { - drop_ranges.iter().map(|(k, v)| (*k, v.trimmed(trim_from))).collect() -} - -#[derive(Clone, Debug, PartialEq, Eq)] -enum Event { - Drop(usize), - Reinit(usize), -} - -impl Event { - fn location(&self) -> usize { - match *self { - Event::Drop(i) | Event::Reinit(i) => i, - } - } -} - -impl PartialOrd for Event { - fn partial_cmp(&self, other: &Self) -> Option { - self.location().partial_cmp(&other.location()) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct DropRange { - events: Vec, -} - -impl DropRange { - fn empty() -> Self { - Self { events: vec![] } - } - - fn intersect(&self, other: &Self) -> Self { - let mut events = vec![]; - self.events - .iter() - .merge_join_by(other.events.iter(), |a, b| a.partial_cmp(b).unwrap()) - .fold((false, false), |(left, right), event| match event { - itertools::EitherOrBoth::Both(_, _) => todo!(), - itertools::EitherOrBoth::Left(e) => match e { - Event::Drop(i) => { - if !left && right { - events.push(Event::Drop(*i)); - } - (true, right) - } - Event::Reinit(i) => { - if left && !right { - events.push(Event::Reinit(*i)); - } - (false, right) - } - }, - itertools::EitherOrBoth::Right(e) => match e { - Event::Drop(i) => { - if left && !right { - events.push(Event::Drop(*i)); - } - (left, true) - } - Event::Reinit(i) => { - if !left && right { - events.push(Event::Reinit(*i)); - } - (left, false) - } - }, - }); - Self { events } - } - - fn is_dropped_at(&self, id: usize) -> bool { - let dropped = match self.events.iter().try_fold(false, |is_dropped, event| { - if event.location() <= id { - Ok(match event { - Event::Drop(_) => true, - Event::Reinit(_) => false, - }) - } else { - Err(is_dropped) - } - }) { - Ok(is_dropped) | Err(is_dropped) => is_dropped, - }; - trace!("is_dropped_at({}): events = {:?}, dropped = {}", id, self.events, dropped); - dropped - } - - fn drop(&mut self, location: usize) { - self.events.push(Event::Drop(location)) - } - - fn reinit(&mut self, location: usize) { - self.events.push(Event::Reinit(location)); - } - - /// Merges another range with this one. Meant to be used at control flow join points. - /// - /// After merging, the value will be dead at the end of the range only if it was dead - /// at the end of both self and other. - #[tracing::instrument] - fn merge_with(&mut self, other: &DropRange, join_point: usize) { - let join_event = if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { - Event::Drop(join_point) - } else { - Event::Reinit(join_point) - }; - let events: Vec<_> = self - .events - .iter() - .merge([join_event].iter()) - .merge(other.events.iter()) - .dedup() - .cloned() - .collect(); - - trace!("events after merging: {:?}", events); - - self.events = events; - } - - /// Creates a new DropRange from this one at the split point. - /// - /// Used to model branching control flow. - fn fork_at(&self, split_point: usize) -> Self { - let result = Self { - events: vec![if self.is_dropped_at(split_point) { - Event::Drop(split_point) - } else { - Event::Reinit(split_point) - }], - }; - trace!("forking at {}: {:?}; result = {:?}", split_point, self.events, result); - result - } - - fn trimmed(&self, trim_from: usize) -> Self { - let start = if self.is_dropped_at(trim_from) { - Event::Drop(trim_from) - } else { - Event::Reinit(trim_from) - }; - - let result = Self { - events: [start] - .iter() - .chain(self.events.iter().skip_while(|event| event.location() <= trim_from)) - .cloned() - .collect(), - }; - trace!("trimmed {:?} at {}, got {:?}", self, trim_from, result); - result - } -} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs new file mode 100644 index 0000000000000..dadd9b8d75367 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -0,0 +1,173 @@ +use rustc_hir::{HirId, HirIdMap}; +use rustc_index::bit_set::BitSet; +use rustc_index::vec::IndexVec; +use rustc_middle::hir::map::Map; + +use super::for_each_consumable; + +rustc_index::newtype_index! { + pub struct PostOrderId { + DEBUG_FORMAT = "id({})", + } +} + +rustc_index::newtype_index! { + pub struct HirIdIndex { + DEBUG_FORMAT = "hidx({})", + } +} + +pub struct DropRanges { + hir_id_map: HirIdMap, + nodes: IndexVec, +} + +/// DropRanges keeps track of what values are definitely dropped at each point in the code. +/// +/// Values of interest are defined by the hir_id of their place. Locations in code are identified +/// by their index in the post-order traversal. At its core, DropRanges maps +/// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely +/// dropped at the point of the node identified by post_order_id. +impl DropRanges { + pub fn new(hir_ids: impl Iterator, hir: &Map<'_>) -> Self { + let mut hir_id_map = HirIdMap::::default(); + let mut next = <_>::from(0u32); + for hir_id in hir_ids { + for_each_consumable(hir_id, hir.find(hir_id), |hir_id| { + if !hir_id_map.contains_key(&hir_id) { + hir_id_map.insert(hir_id, next); + next = <_>::from(next.index() + 1); + } + }); + } + debug!("hir_id_map: {:?}", hir_id_map); + Self { hir_id_map, nodes: <_>::default() } + } + + fn hidx(&self, hir_id: HirId) -> HirIdIndex { + *self.hir_id_map.get(&hir_id).unwrap() + } + + pub fn is_dropped_at(&mut self, hir_id: HirId, location: usize) -> bool { + self.hir_id_map + .get(&hir_id) + .copied() + .map_or(false, |hir_id| self.node(location.into()).drop_state.contains(hir_id)) + } + + /// Returns the number of values (hir_ids) that are tracked + fn num_values(&self) -> usize { + self.hir_id_map.len() + } + + fn node(&mut self, id: PostOrderId) -> &NodeInfo { + let size = self.num_values(); + self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); + &self.nodes[id] + } + + fn node_mut(&mut self, id: PostOrderId) -> &mut NodeInfo { + let size = self.num_values(); + self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); + &mut self.nodes[id] + } + + pub fn add_control_edge(&mut self, from: usize, to: usize) { + self.node_mut(from.into()).successors.push(to.into()); + } + + pub fn drop_at(&mut self, value: HirId, location: usize) { + let value = self.hidx(value); + self.node_mut(location.into()).drops.push(value); + } + + pub fn reinit_at(&mut self, value: HirId, location: usize) { + let value = match self.hir_id_map.get(&value) { + Some(value) => *value, + // If there's no value, this is never consumed and therefore is never dropped. We can + // ignore this. + None => return, + }; + self.node_mut(location.into()).reinits.push(value); + } + + pub fn propagate_to_fixpoint(&mut self) { + while self.propagate() {} + } + + fn propagate(&mut self) -> bool { + let mut visited = BitSet::new_empty(self.nodes.len()); + + self.visit(&mut visited, PostOrderId::from(0usize), PostOrderId::from(0usize), false) + } + + fn visit( + &mut self, + visited: &mut BitSet, + id: PostOrderId, + pred_id: PostOrderId, + mut changed: bool, + ) -> bool { + if visited.contains(id) { + return changed; + } + visited.insert(id); + + changed &= self.nodes[id].merge_with(&self.nodes[pred_id]); + + if self.nodes[id].successors.len() == 0 { + self.visit(visited, PostOrderId::from(id.index() + 1), id, changed) + } else { + self.nodes[id] + .successors + .iter() + .fold(changed, |changed, &succ| self.visit(visited, succ, id, changed)) + } + } +} + +struct NodeInfo { + /// IDs of nodes that can follow this one in the control flow + /// + /// If the vec is empty, then control proceeds to the next node. + successors: Vec, + + /// List of hir_ids that are dropped by this node. + drops: Vec, + + /// List of hir_ids that are reinitialized by this node. + reinits: Vec, + + /// Set of values that are definitely dropped at this point. + drop_state: BitSet, +} + +impl NodeInfo { + fn new(num_values: usize) -> Self { + Self { + successors: vec![], + drops: vec![], + reinits: vec![], + drop_state: BitSet::new_empty(num_values), + } + } + + fn merge_with(&mut self, other: &NodeInfo) -> bool { + let mut changed = false; + for place in &self.drops { + if !self.drop_state.contains(place) && !self.reinits.contains(&place) { + changed = true; + self.drop_state.insert(place); + } + } + + for place in &self.reinits { + if self.drop_state.contains(place) { + changed = true; + self.drop_state.remove(place); + } + } + + changed + } +} From c7afaa1686521b1d812646a4ca7005f408dd5d71 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 17 Nov 2021 11:39:27 -0800 Subject: [PATCH 17/69] Handle break and continue. Change fixpoint computation to handle unreachable nodes. --- .../src/check/generator_interior.rs | 38 +++-- .../check/generator_interior/drop_ranges.rs | 160 ++++++++++++------ 2 files changed, 134 insertions(+), 64 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 33533ab6189f0..1df45b566ce75 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -246,7 +246,12 @@ pub fn resolve_interior<'a, 'tcx>( ) .consume_body(body); - let mut drop_range_visitor = DropRangeVisitor::from(expr_use_visitor); + let region_scope_tree = fcx.tcx.region_scope_tree(def_id); + + let mut drop_range_visitor = DropRangeVisitor::from( + expr_use_visitor, + region_scope_tree.body_expr_count(body.id()).unwrap_or(0), + ); intravisit::walk_body(&mut drop_range_visitor, body); drop_range_visitor.drop_ranges.propagate_to_fixpoint(); @@ -254,7 +259,7 @@ pub fn resolve_interior<'a, 'tcx>( InteriorVisitor { fcx, types: FxIndexSet::default(), - region_scope_tree: fcx.tcx.region_scope_tree(def_id), + region_scope_tree, expr_count: 0, kind, prev_unresolved_span: None, @@ -699,11 +704,12 @@ struct DropRangeVisitor<'tcx> { } impl<'tcx> DropRangeVisitor<'tcx> { - fn from(uses: ExprUseDelegate<'tcx>) -> Self { + fn from(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { debug!("consumed_places: {:?}", uses.consumed_places); let drop_ranges = DropRanges::new( uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), &uses.hir, + num_exprs, ); Self { hir: uses.hir, @@ -851,18 +857,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); - let fork = self.expr_count - 1; + let fork = self.expr_count; - self.drop_ranges.add_control_edge(fork, self.expr_count); + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); self.visit_expr(if_true); - let true_end = self.expr_count - 1; + let true_end = self.expr_count; + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); if let Some(if_false) = if_false { - self.drop_ranges.add_control_edge(fork, self.expr_count); self.visit_expr(if_false); } - self.drop_ranges.add_control_edge(true_end, self.expr_count); + self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); } ExprKind::Assign(lhs, rhs, _) => { self.visit_expr(lhs); @@ -873,13 +879,13 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { ExprKind::Loop(body, ..) => { let loop_begin = self.expr_count; self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_count - 1, loop_begin); + self.drop_ranges.add_control_edge(self.expr_count, loop_begin); } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); let fork = self.expr_count - 1; - let arm_drops = arms + let arm_end_ids = arms .iter() .map(|Arm { pat, body, guard, .. }| { self.drop_ranges.add_control_edge(fork, self.expr_count); @@ -893,16 +899,22 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { None => (), } self.visit_expr(body); - self.expr_count - 1 + self.expr_count }) .collect::>(); - arm_drops.into_iter().for_each(|arm_end| { - self.drop_ranges.add_control_edge(arm_end, self.expr_count) + arm_end_ids.into_iter().for_each(|arm_end| { + self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) }); } + ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) + | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { + self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); + } + _ => intravisit::walk_expr(self, expr), } + self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); self.expr_count += 1; self.consume_expr(expr); if let Some(expr) = reinit { diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index dadd9b8d75367..504734080b69d 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -1,3 +1,7 @@ +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::mem::swap; + use rustc_hir::{HirId, HirIdMap}; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; @@ -20,6 +24,19 @@ rustc_index::newtype_index! { pub struct DropRanges { hir_id_map: HirIdMap, nodes: IndexVec, + deferred_edges: Vec<(usize, HirId)>, + // FIXME: This should only be used for loops and break/continue. + post_order_map: HirIdMap, +} + +impl Debug for DropRanges { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DropRanges") + .field("hir_id_map", &self.hir_id_map) + .field("post_order_maps", &self.post_order_map) + .field("nodes", &self.nodes.iter_enumerated().collect::>()) + .finish() + } } /// DropRanges keeps track of what values are definitely dropped at each point in the code. @@ -29,7 +46,7 @@ pub struct DropRanges { /// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely /// dropped at the point of the node identified by post_order_id. impl DropRanges { - pub fn new(hir_ids: impl Iterator, hir: &Map<'_>) -> Self { + pub fn new(hir_ids: impl Iterator, hir: &Map<'_>, num_exprs: usize) -> Self { let mut hir_id_map = HirIdMap::::default(); let mut next = <_>::from(0u32); for hir_id in hir_ids { @@ -41,7 +58,13 @@ impl DropRanges { }); } debug!("hir_id_map: {:?}", hir_id_map); - Self { hir_id_map, nodes: <_>::default() } + let num_values = hir_id_map.len(); + Self { + hir_id_map, + nodes: IndexVec::from_fn_n(|_| NodeInfo::new(num_values), num_exprs + 1), + deferred_edges: <_>::default(), + post_order_map: <_>::default(), + } } fn hidx(&self, hir_id: HirId) -> HirIdIndex { @@ -52,7 +75,7 @@ impl DropRanges { self.hir_id_map .get(&hir_id) .copied() - .map_or(false, |hir_id| self.node(location.into()).drop_state.contains(hir_id)) + .map_or(false, |hir_id| self.expect_node(location.into()).drop_state.contains(hir_id)) } /// Returns the number of values (hir_ids) that are tracked @@ -60,9 +83,15 @@ impl DropRanges { self.hir_id_map.len() } - fn node(&mut self, id: PostOrderId) -> &NodeInfo { - let size = self.num_values(); - self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); + /// Adds an entry in the mapping from HirIds to PostOrderIds + /// + /// Needed so that `add_control_edge_hir_id` can work. + pub fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: usize) { + self.post_order_map.insert(hir_id, post_order_id); + } + + /// Returns a reference to the NodeInfo for a node, panicking if it does not exist + fn expect_node(&self, id: PostOrderId) -> &NodeInfo { &self.nodes[id] } @@ -73,9 +102,32 @@ impl DropRanges { } pub fn add_control_edge(&mut self, from: usize, to: usize) { + trace!("adding control edge from {} to {}", from, to); self.node_mut(from.into()).successors.push(to.into()); } + /// Like add_control_edge, but uses a hir_id as the target. + /// + /// This can be used for branches where we do not know the PostOrderId of the target yet, + /// such as when handling `break` or `continue`. + pub fn add_control_edge_hir_id(&mut self, from: usize, to: HirId) { + self.deferred_edges.push((from, to)); + } + + /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them. + /// + /// Should be called after visiting the HIR but before solving the control flow, otherwise some + /// edges will be missed. + fn process_deferred_edges(&mut self) { + let mut edges = vec![]; + swap(&mut edges, &mut self.deferred_edges); + edges.into_iter().for_each(|(from, to)| { + let to = *self.post_order_map.get(&to).expect("Expression ID not found"); + trace!("Adding deferred edge from {} to {}", from, to); + self.add_control_edge(from, to) + }); + } + pub fn drop_at(&mut self, value: HirId, location: usize) { let value = self.hidx(value); self.node_mut(location.into()).drops.push(value); @@ -92,40 +144,65 @@ impl DropRanges { } pub fn propagate_to_fixpoint(&mut self) { - while self.propagate() {} - } + trace!("before fixpoint: {:#?}", self); + self.process_deferred_edges(); + let preds = self.compute_predecessors(); + + trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); + + let mut propagate = || { + let mut changed = false; + for id in self.nodes.indices() { + let old_state = self.nodes[id].drop_state.clone(); + if preds[id].len() != 0 { + self.nodes[id].drop_state = self.nodes[preds[id][0]].drop_state.clone(); + for pred in &preds[id][1..] { + let state = self.nodes[*pred].drop_state.clone(); + self.nodes[id].drop_state.intersect(&state); + } + } else { + self.nodes[id].drop_state = if id.index() == 0 { + BitSet::new_empty(self.num_values()) + } else { + // If we are not the start node and we have no predecessors, treat + // everything as dropped because there's no way to get here anyway. + BitSet::new_filled(self.num_values()) + }; + }; + for drop in &self.nodes[id].drops.clone() { + self.nodes[id].drop_state.insert(*drop); + } + for reinit in &self.nodes[id].reinits.clone() { + self.nodes[id].drop_state.remove(*reinit); + } - fn propagate(&mut self) -> bool { - let mut visited = BitSet::new_empty(self.nodes.len()); + changed |= old_state != self.nodes[id].drop_state; + } - self.visit(&mut visited, PostOrderId::from(0usize), PostOrderId::from(0usize), false) - } + changed + }; - fn visit( - &mut self, - visited: &mut BitSet, - id: PostOrderId, - pred_id: PostOrderId, - mut changed: bool, - ) -> bool { - if visited.contains(id) { - return changed; - } - visited.insert(id); + while propagate() {} - changed &= self.nodes[id].merge_with(&self.nodes[pred_id]); + trace!("after fixpoint: {:#?}", self); + } - if self.nodes[id].successors.len() == 0 { - self.visit(visited, PostOrderId::from(id.index() + 1), id, changed) - } else { - self.nodes[id] - .successors - .iter() - .fold(changed, |changed, &succ| self.visit(visited, succ, id, changed)) + fn compute_predecessors(&self) -> IndexVec> { + let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len()); + for (id, node) in self.nodes.iter_enumerated() { + if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 { + preds[<_>::from(id.index() + 1)].push(id); + } else { + for succ in &node.successors { + preds[*succ].push(id); + } + } } + preds } } +#[derive(Debug)] struct NodeInfo { /// IDs of nodes that can follow this one in the control flow /// @@ -148,26 +225,7 @@ impl NodeInfo { successors: vec![], drops: vec![], reinits: vec![], - drop_state: BitSet::new_empty(num_values), + drop_state: BitSet::new_filled(num_values), } } - - fn merge_with(&mut self, other: &NodeInfo) -> bool { - let mut changed = false; - for place in &self.drops { - if !self.drop_state.contains(place) && !self.reinits.contains(&place) { - changed = true; - self.drop_state.insert(place); - } - } - - for place in &self.reinits { - if self.drop_state.contains(place) { - changed = true; - self.drop_state.remove(place); - } - } - - changed - } } From b39fb9bb7bfed503e85c44913e0fe57825f380fb Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 17 Nov 2021 17:53:47 -0800 Subject: [PATCH 18/69] Fix control flow handling in generator_interior All tests pass now! The issue was that we weren't handling all edges correctly, but now they are handled consistently. This includes code to dump a graphviz file for the CFG we built for drop tracking. Also removes old DropRanges tests. --- Cargo.lock | 1 + compiler/rustc_typeck/Cargo.toml | 1 + .../src/check/generator_interior.rs | 12 ++-- .../check/generator_interior/drop_ranges.rs | 72 ++++++++++++++++++- .../src/check/generator_interior/tests.rs | 14 ---- 5 files changed, 78 insertions(+), 22 deletions(-) delete mode 100644 compiler/rustc_typeck/src/check/generator_interior/tests.rs diff --git a/Cargo.lock b/Cargo.lock index b46696dde7cf4..cde44f96ce76f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4388,6 +4388,7 @@ dependencies = [ "rustc_attr", "rustc_data_structures", "rustc_errors", + "rustc_graphviz", "rustc_hir", "rustc_hir_pretty", "rustc_index", diff --git a/compiler/rustc_typeck/Cargo.toml b/compiler/rustc_typeck/Cargo.toml index 3279c331ade18..57930a28a35a1 100644 --- a/compiler/rustc_typeck/Cargo.toml +++ b/compiler/rustc_typeck/Cargo.toml @@ -15,6 +15,7 @@ rustc_middle = { path = "../rustc_middle" } rustc_attr = { path = "../rustc_attr" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } +rustc_graphviz = { path = "../rustc_graphviz" } rustc_hir = { path = "../rustc_hir" } rustc_hir_pretty = { path = "../rustc_hir_pretty" } rustc_target = { path = "../rustc_target" } diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 1df45b566ce75..f26ba875c01d0 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -26,9 +26,6 @@ use rustc_span::Span; use smallvec::SmallVec; use tracing::debug; -#[cfg(test)] -mod tests; - mod drop_ranges; struct InteriorVisitor<'a, 'tcx> { @@ -255,6 +252,7 @@ pub fn resolve_interior<'a, 'tcx>( intravisit::walk_body(&mut drop_range_visitor, body); drop_range_visitor.drop_ranges.propagate_to_fixpoint(); + // drop_range_visitor.drop_ranges.save_graph("drop_ranges.dot"); InteriorVisitor { fcx, @@ -877,18 +875,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } ExprKind::Loop(body, ..) => { - let loop_begin = self.expr_count; + let loop_begin = self.expr_count + 1; self.visit_block(body); self.drop_ranges.add_control_edge(self.expr_count, loop_begin); } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); - let fork = self.expr_count - 1; + let fork = self.expr_count; let arm_end_ids = arms .iter() .map(|Arm { pat, body, guard, .. }| { - self.drop_ranges.add_control_edge(fork, self.expr_count); + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); self.visit_pat(pat); match guard { Some(Guard::If(expr)) => self.visit_expr(expr), @@ -914,8 +912,8 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { _ => intravisit::walk_expr(self, expr), } - self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); self.expr_count += 1; + self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); self.consume_expr(expr); if let Some(expr) = reinit { self.reinit_expr(expr); diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 504734080b69d..5fe3e4088385d 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::fmt::Debug; use std::mem::swap; +use rustc_graphviz as dot; use rustc_hir::{HirId, HirIdMap}; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; @@ -182,7 +183,9 @@ impl DropRanges { changed }; - while propagate() {} + while propagate() { + trace!("drop_state changed, re-running propagation"); + } trace!("after fixpoint: {:#?}", self); } @@ -200,6 +203,73 @@ impl DropRanges { } preds } + + // pub fn save_graph(&self, filename: &str) { + // use std::fs::File; + // dot::render(self, &mut File::create(filename).unwrap()).unwrap(); + // } +} + +impl<'a> dot::GraphWalk<'a> for DropRanges { + type Node = PostOrderId; + + type Edge = (PostOrderId, PostOrderId); + + fn nodes(&'a self) -> dot::Nodes<'a, Self::Node> { + self.nodes.iter_enumerated().map(|(i, _)| i).collect() + } + + fn edges(&'a self) -> dot::Edges<'a, Self::Edge> { + self.nodes + .iter_enumerated() + .flat_map(|(i, node)| { + if node.successors.len() == 0 { + vec![(i, PostOrderId::from_usize(i.index() + 1))] + } else { + node.successors.iter().map(move |&s| (i, s)).collect() + } + }) + .collect() + } + + fn source(&'a self, edge: &Self::Edge) -> Self::Node { + edge.0 + } + + fn target(&'a self, edge: &Self::Edge) -> Self::Node { + edge.1 + } +} + +impl<'a> dot::Labeller<'a> for DropRanges { + type Node = PostOrderId; + + type Edge = (PostOrderId, PostOrderId); + + fn graph_id(&'a self) -> dot::Id<'a> { + dot::Id::new("drop_ranges").unwrap() + } + + fn node_id(&'a self, n: &Self::Node) -> dot::Id<'a> { + dot::Id::new(format!("id{}", n.index())).unwrap() + } + + fn node_label(&'a self, n: &Self::Node) -> dot::LabelText<'a> { + dot::LabelText::LabelStr( + format!( + "{:?}, local_id: {}", + n, + self.post_order_map + .iter() + .find(|(_hir_id, &post_order_id)| post_order_id == n.index()) + .map_or("".into(), |(hir_id, _)| format!( + "{}", + hir_id.local_id.index() + )) + ) + .into(), + ) + } } #[derive(Debug)] diff --git a/compiler/rustc_typeck/src/check/generator_interior/tests.rs b/compiler/rustc_typeck/src/check/generator_interior/tests.rs deleted file mode 100644 index 8f973bb94895a..0000000000000 --- a/compiler/rustc_typeck/src/check/generator_interior/tests.rs +++ /dev/null @@ -1,14 +0,0 @@ -use super::DropRange; - -#[test] -fn drop_range_uses_last_event() { - let mut range = DropRange::empty(); - range.drop(10); - range.reinit(10); - assert!(!range.is_dropped_at(10)); - - let mut range = DropRange::empty(); - range.reinit(10); - range.drop(10); - assert!(range.is_dropped_at(10)); -} From 904c2701496143e089e01b3a10ccbe7eaa1bf9e5 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 18 Nov 2021 17:26:30 -0800 Subject: [PATCH 19/69] More comments and small cleanups --- compiler/rustc_passes/src/region.rs | 1 + .../src/check/generator_interior.rs | 214 +++++++++--------- .../check/generator_interior/drop_ranges.rs | 5 - 3 files changed, 108 insertions(+), 112 deletions(-) diff --git a/compiler/rustc_passes/src/region.rs b/compiler/rustc_passes/src/region.rs index 8b22c46f01ba6..fdf93e5893247 100644 --- a/compiler/rustc_passes/src/region.rs +++ b/compiler/rustc_passes/src/region.rs @@ -255,6 +255,7 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h hir::ExprKind::AssignOp(..) | hir::ExprKind::Index(..) | hir::ExprKind::Unary(..) + | hir::ExprKind::Call(..) | hir::ExprKind::MethodCall(..) => { // FIXME(https://github.com/rust-lang/rfcs/issues/811) Nested method calls // diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index f26ba875c01d0..0df56dd2ee815 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -182,39 +182,6 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { } } } - - fn visit_call( - &mut self, - call_expr: &'tcx Expr<'tcx>, - callee: &'tcx Expr<'tcx>, - args: &'tcx [Expr<'tcx>], - ) { - match &callee.kind { - ExprKind::Path(qpath) => { - let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id); - match res { - // Direct calls never need to keep the callee `ty::FnDef` - // ZST in a temporary, so skip its type, just in case it - // can significantly complicate the generator type. - Res::Def( - DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn), - _, - ) => { - // NOTE(eddyb) this assumes a path expression has - // no nested expressions to keep track of. - self.expr_count += 1; - - // Record the rest of the call expression normally. - for arg in args { - self.visit_expr(arg); - } - } - _ => intravisit::walk_expr(self, call_expr), - } - } - _ => intravisit::walk_expr(self, call_expr), - } - } } pub fn resolve_interior<'a, 'tcx>( @@ -252,7 +219,6 @@ pub fn resolve_interior<'a, 'tcx>( intravisit::walk_body(&mut drop_range_visitor, body); drop_range_visitor.drop_ranges.propagate_to_fixpoint(); - // drop_range_visitor.drop_ranges.save_graph("drop_ranges.dot"); InteriorVisitor { fcx, @@ -395,7 +361,31 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> { let mut guard_borrowing_from_pattern = false; match &expr.kind { - ExprKind::Call(callee, args) => self.visit_call(expr, callee, args), + ExprKind::Call(callee, args) => match &callee.kind { + ExprKind::Path(qpath) => { + let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id); + match res { + // Direct calls never need to keep the callee `ty::FnDef` + // ZST in a temporary, so skip its type, just in case it + // can significantly complicate the generator type. + Res::Def( + DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn), + _, + ) => { + // NOTE(eddyb) this assumes a path expression has + // no nested expressions to keep track of. + self.expr_count += 1; + + // Record the rest of the call expression normally. + for arg in args.iter() { + self.visit_expr(arg); + } + } + _ => intravisit::walk_expr(self, expr), + } + } + _ => intravisit::walk_expr(self, expr), + }, ExprKind::Path(qpath) => { intravisit::walk_expr(self, expr); let res = self.fcx.typeck_results.borrow().qpath_res(qpath, expr.hir_id); @@ -675,6 +665,26 @@ fn check_must_not_suspend_def( false } +// The following structs and impls are used for drop range analysis. +// +// Drop range analysis finds the portions of the tree where a value is guaranteed to be dropped +// (i.e. moved, uninitialized, etc.). This is used to exclude the types of those values from the +// generator type. See `InteriorVisitor::record` for where the results of this analysis are used. +// +// There are three phases to this analysis: +// 1. Use `ExprUseVisitor` to identify the interesting values that are consumed and borrowed. +// 2. Use `DropRangeVisitor` to find where the interesting values are dropped or reinitialized, +// and also build a control flow graph. +// 3. Use `DropRanges::propagate_to_fixpoint` to flow the dropped/reinitialized information through +// the CFG and find the exact points where we know a value is definitely dropped. +// +// The end result is a data structure that maps the post-order index of each node in the HIR tree +// to a set of values that are known to be dropped at that location. + +/// Works with ExprUseVisitor to find interesting values for the drop range analysis. +/// +/// Interesting values are those that are either dropped or borrowed. For dropped values, we also +/// record the parent expression, which is the point where the drop actually takes place. struct ExprUseDelegate<'tcx> { hir: Map<'tcx>, /// Maps a HirId to a set of HirIds that are dropped by that node. @@ -691,7 +701,65 @@ impl<'tcx> ExprUseDelegate<'tcx> { } } -/// This struct facilitates computing the ranges for which a place is uninitialized. +impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { + fn consume( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + diag_expr_id: hir::HirId, + ) { + let parent = match self.hir.find_parent_node(place_with_id.hir_id) { + Some(parent) => parent, + None => place_with_id.hir_id, + }; + debug!( + "consume {:?}; diag_expr_id={:?}, using parent {:?}", + place_with_id, diag_expr_id, parent + ); + self.mark_consumed(parent, place_with_id.hir_id); + place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); + } + + fn borrow( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + _bk: rustc_middle::ty::BorrowKind, + ) { + place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); + } + + fn mutate( + &mut self, + _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + ) { + } + + fn fake_read( + &mut self, + _place: expr_use_visitor::Place<'tcx>, + _cause: rustc_middle::mir::FakeReadCause, + _diag_expr_id: hir::HirId, + ) { + } +} + +/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to +/// track for a value in the drop range analysis. +fn place_hir_id(place: &Place<'_>) -> Option { + match place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => None, + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), + } +} + +/// This struct is used to gather the information for `DropRanges` to determine the regions of the +/// HIR tree for which a value is dropped. +/// +/// We are interested in points where a variables is dropped or initialized, and the control flow +/// of the code. We identify locations in code by their post-order traversal index, so it is +/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. struct DropRangeVisitor<'tcx> { hir: Map<'tcx>, /// Maps a HirId to a set of HirIds that are dropped by that node. @@ -756,6 +824,9 @@ impl<'tcx> DropRangeVisitor<'tcx> { } } +/// Applies `f` to consumable portion of a HIR node. +/// +/// The `node` parameter should be the result of calling `Map::find(place)`. fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(HirId)) { f(place); if let Some(Node::Expr(expr)) = node { @@ -771,57 +842,6 @@ fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(H } } -fn place_hir_id(place: &Place<'_>) -> Option { - match place.base { - PlaceBase::Rvalue | PlaceBase::StaticItem => None, - PlaceBase::Local(hir_id) - | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), - } -} - -impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { - fn consume( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - diag_expr_id: hir::HirId, - ) { - let parent = match self.hir.find_parent_node(place_with_id.hir_id) { - Some(parent) => parent, - None => place_with_id.hir_id, - }; - debug!( - "consume {:?}; diag_expr_id={:?}, using parent {:?}", - place_with_id, diag_expr_id, parent - ); - self.mark_consumed(parent, place_with_id.hir_id); - place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); - } - - fn borrow( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - _bk: rustc_middle::ty::BorrowKind, - ) { - place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); - } - - fn mutate( - &mut self, - _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - ) { - } - - fn fake_read( - &mut self, - _place: expr_use_visitor::Place<'tcx>, - _cause: rustc_middle::mir::FakeReadCause, - _diag_expr_id: hir::HirId, - ) { - } -} - impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { type Map = intravisit::ErasedMap<'tcx>; @@ -832,26 +852,6 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut reinit = None; match expr.kind { - // ExprKind::AssignOp(_op, lhs, rhs) => { - // // These operations are weird because their order of evaluation depends on whether - // // the operator is overloaded. In a perfect world, we'd just ask the type checker - // // whether this is a method call, but we also need to match the expression IDs - // // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, - // // so it runs both orders and picks the most conservative. We'll mirror that here. - // let mut old_count = self.expr_count; - // self.visit_expr(lhs); - // self.visit_expr(rhs); - - // let old_drops = self.swap_drop_ranges(<_>::default()); - // std::mem::swap(&mut old_count, &mut self.expr_count); - // self.visit_expr(rhs); - // self.visit_expr(lhs); - - // // We should have visited the same number of expressions in either order. - // assert_eq!(old_count, self.expr_count); - - // self.intersect_drop_ranges(old_drops); - // } ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 5fe3e4088385d..d497210b43461 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -203,11 +203,6 @@ impl DropRanges { } preds } - - // pub fn save_graph(&self, filename: &str) { - // use std::fs::File; - // dot::render(self, &mut File::create(filename).unwrap()).unwrap(); - // } } impl<'a> dot::GraphWalk<'a> for DropRanges { From 5feb4d0106b1a7022f2fd4379a1a5abfc8926d99 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 18 Nov 2021 18:33:40 -0800 Subject: [PATCH 20/69] Refactor code to keep most drop range analysis in drop_ranges.rs --- .../src/check/generator_interior.rs | 288 +----------------- .../check/generator_interior/drop_ranges.rs | 277 ++++++++++++++++- 2 files changed, 283 insertions(+), 282 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 0df56dd2ee815..68269f24e9d33 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,12 +3,9 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. -use crate::expr_use_visitor::{self, ExprUseVisitor}; - -use self::drop_ranges::DropRanges; - +use self::drop_ranges::{DropRangeVisitor, DropRanges, ExprUseDelegate}; use super::FnCtxt; -use hir::{HirIdMap, Node}; +use crate::expr_use_visitor::ExprUseVisitor; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -17,8 +14,6 @@ use rustc_hir::def_id::DefId; use rustc_hir::hir_id::HirIdSet; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind}; -use rustc_middle::hir::map::Map; -use rustc_middle::hir::place::{Place, PlaceBase}; use rustc_middle::middle::region::{self, YieldData}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::symbol::sym; @@ -194,11 +189,7 @@ pub fn resolve_interior<'a, 'tcx>( let body = fcx.tcx.hir().body(body_id); let mut visitor = { - let mut expr_use_visitor = ExprUseDelegate { - hir: fcx.tcx.hir(), - consumed_places: <_>::default(), - borrowed_places: <_>::default(), - }; + let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx.hir()); // Run ExprUseVisitor to find where values are consumed. ExprUseVisitor::new( @@ -211,14 +202,14 @@ pub fn resolve_interior<'a, 'tcx>( .consume_body(body); let region_scope_tree = fcx.tcx.region_scope_tree(def_id); - - let mut drop_range_visitor = DropRangeVisitor::from( + let mut drop_range_visitor = DropRangeVisitor::from_uses( expr_use_visitor, region_scope_tree.body_expr_count(body.id()).unwrap_or(0), ); intravisit::walk_body(&mut drop_range_visitor, body); - drop_range_visitor.drop_ranges.propagate_to_fixpoint(); + let mut drop_ranges = drop_range_visitor.into_drop_ranges(); + drop_ranges.propagate_to_fixpoint(); InteriorVisitor { fcx, @@ -230,7 +221,7 @@ pub fn resolve_interior<'a, 'tcx>( guard_bindings: <_>::default(), guard_bindings_set: <_>::default(), linted_values: <_>::default(), - drop_ranges: drop_range_visitor.drop_ranges, + drop_ranges: drop_ranges, } }; intravisit::walk_body(&mut visitor, body); @@ -377,7 +368,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> { self.expr_count += 1; // Record the rest of the call expression normally. - for arg in args.iter() { + for arg in *args { self.visit_expr(arg); } } @@ -664,266 +655,3 @@ fn check_must_not_suspend_def( } false } - -// The following structs and impls are used for drop range analysis. -// -// Drop range analysis finds the portions of the tree where a value is guaranteed to be dropped -// (i.e. moved, uninitialized, etc.). This is used to exclude the types of those values from the -// generator type. See `InteriorVisitor::record` for where the results of this analysis are used. -// -// There are three phases to this analysis: -// 1. Use `ExprUseVisitor` to identify the interesting values that are consumed and borrowed. -// 2. Use `DropRangeVisitor` to find where the interesting values are dropped or reinitialized, -// and also build a control flow graph. -// 3. Use `DropRanges::propagate_to_fixpoint` to flow the dropped/reinitialized information through -// the CFG and find the exact points where we know a value is definitely dropped. -// -// The end result is a data structure that maps the post-order index of each node in the HIR tree -// to a set of values that are known to be dropped at that location. - -/// Works with ExprUseVisitor to find interesting values for the drop range analysis. -/// -/// Interesting values are those that are either dropped or borrowed. For dropped values, we also -/// record the parent expression, which is the point where the drop actually takes place. -struct ExprUseDelegate<'tcx> { - hir: Map<'tcx>, - /// Maps a HirId to a set of HirIds that are dropped by that node. - consumed_places: HirIdMap, - borrowed_places: HirIdSet, -} - -impl<'tcx> ExprUseDelegate<'tcx> { - fn mark_consumed(&mut self, consumer: HirId, target: HirId) { - if !self.consumed_places.contains_key(&consumer) { - self.consumed_places.insert(consumer, <_>::default()); - } - self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); - } -} - -impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { - fn consume( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - diag_expr_id: hir::HirId, - ) { - let parent = match self.hir.find_parent_node(place_with_id.hir_id) { - Some(parent) => parent, - None => place_with_id.hir_id, - }; - debug!( - "consume {:?}; diag_expr_id={:?}, using parent {:?}", - place_with_id, diag_expr_id, parent - ); - self.mark_consumed(parent, place_with_id.hir_id); - place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); - } - - fn borrow( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - _bk: rustc_middle::ty::BorrowKind, - ) { - place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); - } - - fn mutate( - &mut self, - _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - ) { - } - - fn fake_read( - &mut self, - _place: expr_use_visitor::Place<'tcx>, - _cause: rustc_middle::mir::FakeReadCause, - _diag_expr_id: hir::HirId, - ) { - } -} - -/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to -/// track for a value in the drop range analysis. -fn place_hir_id(place: &Place<'_>) -> Option { - match place.base { - PlaceBase::Rvalue | PlaceBase::StaticItem => None, - PlaceBase::Local(hir_id) - | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), - } -} - -/// This struct is used to gather the information for `DropRanges` to determine the regions of the -/// HIR tree for which a value is dropped. -/// -/// We are interested in points where a variables is dropped or initialized, and the control flow -/// of the code. We identify locations in code by their post-order traversal index, so it is -/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. -struct DropRangeVisitor<'tcx> { - hir: Map<'tcx>, - /// Maps a HirId to a set of HirIds that are dropped by that node. - consumed_places: HirIdMap, - borrowed_places: HirIdSet, - drop_ranges: DropRanges, - expr_count: usize, -} - -impl<'tcx> DropRangeVisitor<'tcx> { - fn from(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { - debug!("consumed_places: {:?}", uses.consumed_places); - let drop_ranges = DropRanges::new( - uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), - &uses.hir, - num_exprs, - ); - Self { - hir: uses.hir, - consumed_places: uses.consumed_places, - borrowed_places: uses.borrowed_places, - drop_ranges, - expr_count: 0, - } - } - - fn record_drop(&mut self, hir_id: HirId) { - if self.borrowed_places.contains(&hir_id) { - debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); - } else { - debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - let count = self.expr_count; - self.drop_ranges.drop_at(hir_id, count); - } - } - - /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all - /// expressions. This method consumes a little deeper into the expression when needed. - fn consume_expr(&mut self, expr: &hir::Expr<'_>) { - debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); - let places = self - .consumed_places - .get(&expr.hir_id) - .map_or(vec![], |places| places.iter().cloned().collect()); - for place in places { - for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); - } - } - - fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { - if let ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) = expr.kind - { - let location = self.expr_count; - debug!("reinitializing {:?} at {}", hir_id, location); - self.drop_ranges.reinit_at(*hir_id, location); - } else { - debug!("reinitializing {:?} is not supported", expr); - } - } -} - -/// Applies `f` to consumable portion of a HIR node. -/// -/// The `node` parameter should be the result of calling `Map::find(place)`. -fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(HirId)) { - f(place); - if let Some(Node::Expr(expr)) = node { - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) => { - f(*hir_id); - } - _ => (), - } - } -} - -impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { - type Map = intravisit::ErasedMap<'tcx>; - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - let mut reinit = None; - match expr.kind { - ExprKind::If(test, if_true, if_false) => { - self.visit_expr(test); - - let fork = self.expr_count; - - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - self.visit_expr(if_true); - let true_end = self.expr_count; - - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - if let Some(if_false) = if_false { - self.visit_expr(if_false); - } - - self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); - } - ExprKind::Assign(lhs, rhs, _) => { - self.visit_expr(lhs); - self.visit_expr(rhs); - - reinit = Some(lhs); - } - ExprKind::Loop(body, ..) => { - let loop_begin = self.expr_count + 1; - self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_count, loop_begin); - } - ExprKind::Match(scrutinee, arms, ..) => { - self.visit_expr(scrutinee); - - let fork = self.expr_count; - let arm_end_ids = arms - .iter() - .map(|Arm { pat, body, guard, .. }| { - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - self.visit_pat(pat); - match guard { - Some(Guard::If(expr)) => self.visit_expr(expr), - Some(Guard::IfLet(pat, expr)) => { - self.visit_pat(pat); - self.visit_expr(expr); - } - None => (), - } - self.visit_expr(body); - self.expr_count - }) - .collect::>(); - arm_end_ids.into_iter().for_each(|arm_end| { - self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) - }); - } - ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) - | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { - self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); - } - - _ => intravisit::walk_expr(self, expr), - } - - self.expr_count += 1; - self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); - self.consume_expr(expr); - if let Some(expr) = reinit { - self.reinit_expr(expr); - } - } - - fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) { - intravisit::walk_pat(self, pat); - - // Increment expr_count here to match what InteriorVisitor expects. - self.expr_count += 1; - } -} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index d497210b43461..708ce82478000 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -1,14 +1,287 @@ +//! Drop range analysis finds the portions of the tree where a value is guaranteed to be dropped +//! (i.e. moved, uninitialized, etc.). This is used to exclude the types of those values from the +//! generator type. See `InteriorVisitor::record` for where the results of this analysis are used. +//! +//! There are three phases to this analysis: +//! 1. Use `ExprUseVisitor` to identify the interesting values that are consumed and borrowed. +//! 2. Use `DropRangeVisitor` to find where the interesting values are dropped or reinitialized, +//! and also build a control flow graph. +//! 3. Use `DropRanges::propagate_to_fixpoint` to flow the dropped/reinitialized information through +//! the CFG and find the exact points where we know a value is definitely dropped. +//! +//! The end result is a data structure that maps the post-order index of each node in the HIR tree +//! to a set of values that are known to be dropped at that location. + use std::collections::BTreeMap; use std::fmt::Debug; use std::mem::swap; +use hir::intravisit::{self, NestedVisitorMap, Visitor}; +use hir::{Expr, ExprKind, Guard, HirId, HirIdMap, HirIdSet, Node}; use rustc_graphviz as dot; -use rustc_hir::{HirId, HirIdMap}; +use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; use rustc_middle::hir::map::Map; +use rustc_middle::hir::place::{Place, PlaceBase}; +use rustc_middle::ty; + +use crate::expr_use_visitor; + +/// Works with ExprUseVisitor to find interesting values for the drop range analysis. +/// +/// Interesting values are those that are either dropped or borrowed. For dropped values, we also +/// record the parent expression, which is the point where the drop actually takes place. +pub struct ExprUseDelegate<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + consumed_places: HirIdMap, + borrowed_places: HirIdSet, +} + +impl<'tcx> ExprUseDelegate<'tcx> { + pub fn new(hir: Map<'tcx>) -> Self { + Self { hir, consumed_places: <_>::default(), borrowed_places: <_>::default() } + } + + fn mark_consumed(&mut self, consumer: HirId, target: HirId) { + if !self.consumed_places.contains_key(&consumer) { + self.consumed_places.insert(consumer, <_>::default()); + } + self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); + } +} + +impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { + fn consume( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + diag_expr_id: hir::HirId, + ) { + let parent = match self.hir.find_parent_node(place_with_id.hir_id) { + Some(parent) => parent, + None => place_with_id.hir_id, + }; + debug!( + "consume {:?}; diag_expr_id={:?}, using parent {:?}", + place_with_id, diag_expr_id, parent + ); + self.mark_consumed(parent, place_with_id.hir_id); + place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); + } + + fn borrow( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + _bk: rustc_middle::ty::BorrowKind, + ) { + place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); + } + + fn mutate( + &mut self, + _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + ) { + } + + fn fake_read( + &mut self, + _place: expr_use_visitor::Place<'tcx>, + _cause: rustc_middle::mir::FakeReadCause, + _diag_expr_id: hir::HirId, + ) { + } +} + +/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to +/// track for a value in the drop range analysis. +fn place_hir_id(place: &Place<'_>) -> Option { + match place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => None, + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), + } +} + +/// This struct is used to gather the information for `DropRanges` to determine the regions of the +/// HIR tree for which a value is dropped. +/// +/// We are interested in points where a variables is dropped or initialized, and the control flow +/// of the code. We identify locations in code by their post-order traversal index, so it is +/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. +pub struct DropRangeVisitor<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + consumed_places: HirIdMap, + borrowed_places: HirIdSet, + drop_ranges: DropRanges, + expr_count: usize, +} + +impl<'tcx> DropRangeVisitor<'tcx> { + pub fn from_uses(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { + debug!("consumed_places: {:?}", uses.consumed_places); + let drop_ranges = DropRanges::new( + uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), + &uses.hir, + num_exprs, + ); + Self { + hir: uses.hir, + consumed_places: uses.consumed_places, + borrowed_places: uses.borrowed_places, + drop_ranges, + expr_count: 0, + } + } + + pub fn into_drop_ranges(self) -> DropRanges { + self.drop_ranges + } + + fn record_drop(&mut self, hir_id: HirId) { + if self.borrowed_places.contains(&hir_id) { + debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); + } else { + debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); + let count = self.expr_count; + self.drop_ranges.drop_at(hir_id, count); + } + } + + /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all + /// expressions. This method consumes a little deeper into the expression when needed. + fn consume_expr(&mut self, expr: &hir::Expr<'_>) { + debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); + let places = self + .consumed_places + .get(&expr.hir_id) + .map_or(vec![], |places| places.iter().cloned().collect()); + for place in places { + for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); + } + } + + fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { + if let ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) = expr.kind + { + let location = self.expr_count; + debug!("reinitializing {:?} at {}", hir_id, location); + self.drop_ranges.reinit_at(*hir_id, location); + } else { + debug!("reinitializing {:?} is not supported", expr); + } + } +} -use super::for_each_consumable; +/// Applies `f` to consumable portion of a HIR node. +/// +/// The `node` parameter should be the result of calling `Map::find(place)`. +fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(HirId)) { + f(place); + if let Some(Node::Expr(expr)) = node { + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + f(*hir_id); + } + _ => (), + } + } +} + +impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { + type Map = intravisit::ErasedMap<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + let mut reinit = None; + match expr.kind { + ExprKind::If(test, if_true, if_false) => { + self.visit_expr(test); + + let fork = self.expr_count; + + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.visit_expr(if_true); + let true_end = self.expr_count; + + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + if let Some(if_false) = if_false { + self.visit_expr(if_false); + } + + self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); + } + ExprKind::Assign(lhs, rhs, _) => { + self.visit_expr(lhs); + self.visit_expr(rhs); + + reinit = Some(lhs); + } + ExprKind::Loop(body, ..) => { + let loop_begin = self.expr_count + 1; + self.visit_block(body); + self.drop_ranges.add_control_edge(self.expr_count, loop_begin); + } + ExprKind::Match(scrutinee, arms, ..) => { + self.visit_expr(scrutinee); + + let fork = self.expr_count; + let arm_end_ids = arms + .iter() + .map(|hir::Arm { pat, body, guard, .. }| { + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.visit_pat(pat); + match guard { + Some(Guard::If(expr)) => self.visit_expr(expr), + Some(Guard::IfLet(pat, expr)) => { + self.visit_pat(pat); + self.visit_expr(expr); + } + None => (), + } + self.visit_expr(body); + self.expr_count + }) + .collect::>(); + arm_end_ids.into_iter().for_each(|arm_end| { + self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) + }); + } + ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) + | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { + self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); + } + + _ => intravisit::walk_expr(self, expr), + } + + self.expr_count += 1; + self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); + self.consume_expr(expr); + if let Some(expr) = reinit { + self.reinit_expr(expr); + } + } + + fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { + intravisit::walk_pat(self, pat); + + // Increment expr_count here to match what InteriorVisitor expects. + self.expr_count += 1; + } +} rustc_index::newtype_index! { pub struct PostOrderId { From 46760b4e673e95a4775775fe7da028a33d0f50ae Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 22 Nov 2021 14:53:02 -0800 Subject: [PATCH 21/69] Update async-fn-nonsend.stderr --- .../ui/async-await/async-fn-nonsend.stderr | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/src/test/ui/async-await/async-fn-nonsend.stderr b/src/test/ui/async-await/async-fn-nonsend.stderr index bff282085735c..abba5585c62eb 100644 --- a/src/test/ui/async-await/async-fn-nonsend.stderr +++ b/src/test/ui/async-await/async-fn-nonsend.stderr @@ -1,28 +1,5 @@ error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:49:17 - | -LL | assert_send(local_dropped_before_await()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `local_dropped_before_await` is not `Send` - | - = help: within `impl Future`, the trait `Send` is not implemented for `Rc<()>` -note: future is not `Send` as this value is used across an await - --> $DIR/async-fn-nonsend.rs:24:10 - | -LL | let x = non_send(); - | - has type `impl Debug` which is not `Send` -LL | drop(x); -LL | fut().await; - | ^^^^^^ await occurs here, with `x` maybe used later -LL | } - | - `x` is later dropped here -note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:46:24 - | -LL | fn assert_send(_: impl Send) {} - | ^^^^ required by this bound in `assert_send` - -error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:51:17 + --> $DIR/async-fn-nonsend.rs:50:17 | LL | assert_send(non_send_temporary_in_match()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_send_temporary_in_match` is not `Send` @@ -32,12 +9,12 @@ note: future is not `Send` as this value is used across an await --> $DIR/async-fn-nonsend.rs:33:25 | LL | match Some(non_send()) { - | ---------- has type `impl Debug` which is not `Send` + | ---------------- has type `Option` which is not `Send` LL | Some(_) => fut().await, - | ^^^^^^ await occurs here, with `non_send()` maybe used later + | ^^^^^^ await occurs here, with `Some(non_send())` maybe used later ... LL | } - | - `non_send()` is later dropped here + | - `Some(non_send())` is later dropped here note: required by a bound in `assert_send` --> $DIR/async-fn-nonsend.rs:46:24 | @@ -45,7 +22,7 @@ LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:53:17 + --> $DIR/async-fn-nonsend.rs:52:17 | LL | assert_send(non_sync_with_method_call()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_sync_with_method_call` is not `Send` @@ -68,5 +45,5 @@ note: required by a bound in `assert_send` LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors From 006f5471629e309c19084698e3bcb494ce60d808 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 6 Dec 2021 11:08:00 -0800 Subject: [PATCH 22/69] Add more comments --- .../src/check/generator_interior/drop_ranges.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 708ce82478000..eb95c4e0b64a2 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -34,8 +34,15 @@ use crate::expr_use_visitor; /// record the parent expression, which is the point where the drop actually takes place. pub struct ExprUseDelegate<'tcx> { hir: Map<'tcx>, - /// Maps a HirId to a set of HirIds that are dropped by that node. + /// Records the point at which an expression or local variable is dropped. + /// + /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables + /// or values that are consumed by that expression. + /// + /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is + /// not considered a drop of `x`. consumed_places: HirIdMap, + /// A set of hir-ids of values or variables that are borrowed at some point within the body. borrowed_places: HirIdSet, } @@ -114,6 +121,8 @@ fn place_hir_id(place: &Place<'_>) -> Option { pub struct DropRangeVisitor<'tcx> { hir: Map<'tcx>, /// Maps a HirId to a set of HirIds that are dropped by that node. + /// + /// See also the more detailed comment on `ExprUseDelegate.consumed_places`. consumed_places: HirIdMap, borrowed_places: HirIdSet, drop_ranges: DropRanges, From 30e1b1e92e3a685567417f812e3a079a9d51422c Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 13 Dec 2021 10:47:28 -0800 Subject: [PATCH 23/69] Address code review comments 1. Add test case for partial drops 2. Simplify code in `propagate_to_fixpoint` and remove most clones 3. Clean up PostOrderIndex creation --- .../check/generator_interior/drop_ranges.rs | 44 +++++++++---------- src/test/ui/generator/partial-drop.rs | 21 +++++++++ 2 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 src/test/ui/generator/partial-drop.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index eb95c4e0b64a2..7b76ff5e02bf7 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -34,13 +34,13 @@ use crate::expr_use_visitor; /// record the parent expression, which is the point where the drop actually takes place. pub struct ExprUseDelegate<'tcx> { hir: Map<'tcx>, - /// Records the point at which an expression or local variable is dropped. + /// Records the variables/expressions that are dropped by a given expression. /// /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables /// or values that are consumed by that expression. /// /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is - /// not considered a drop of `x`. + /// not considered a drop of `x`, although it would be a drop of `x.y`. consumed_places: HirIdMap, /// A set of hir-ids of values or variables that are borrowed at some point within the body. borrowed_places: HirIdSet, @@ -437,29 +437,29 @@ impl DropRanges { let mut changed = false; for id in self.nodes.indices() { let old_state = self.nodes[id].drop_state.clone(); - if preds[id].len() != 0 { - self.nodes[id].drop_state = self.nodes[preds[id][0]].drop_state.clone(); - for pred in &preds[id][1..] { - let state = self.nodes[*pred].drop_state.clone(); - self.nodes[id].drop_state.intersect(&state); - } + let mut new_state = if id.index() == 0 { + BitSet::new_empty(self.num_values()) } else { - self.nodes[id].drop_state = if id.index() == 0 { - BitSet::new_empty(self.num_values()) - } else { - // If we are not the start node and we have no predecessors, treat - // everything as dropped because there's no way to get here anyway. - BitSet::new_filled(self.num_values()) - }; + // If we are not the start node and we have no predecessors, treat + // everything as dropped because there's no way to get here anyway. + BitSet::new_filled(self.num_values()) }; - for drop in &self.nodes[id].drops.clone() { - self.nodes[id].drop_state.insert(*drop); + + for pred in &preds[id] { + let state = &self.nodes[*pred].drop_state; + new_state.intersect(state); + } + + for drop in &self.nodes[id].drops { + new_state.insert(*drop); } - for reinit in &self.nodes[id].reinits.clone() { - self.nodes[id].drop_state.remove(*reinit); + + for reinit in &self.nodes[id].reinits { + new_state.remove(*reinit); } - changed |= old_state != self.nodes[id].drop_state; + changed |= old_state != new_state; + self.nodes[id].drop_state = new_state; } changed @@ -476,7 +476,7 @@ impl DropRanges { let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len()); for (id, node) in self.nodes.iter_enumerated() { if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 { - preds[<_>::from(id.index() + 1)].push(id); + preds[id + 1].push(id); } else { for succ in &node.successors { preds[*succ].push(id); @@ -501,7 +501,7 @@ impl<'a> dot::GraphWalk<'a> for DropRanges { .iter_enumerated() .flat_map(|(i, node)| { if node.successors.len() == 0 { - vec![(i, PostOrderId::from_usize(i.index() + 1))] + vec![(i, i + 1)] } else { node.successors.iter().map(move |&s| (i, s)).collect() } diff --git a/src/test/ui/generator/partial-drop.rs b/src/test/ui/generator/partial-drop.rs new file mode 100644 index 0000000000000..a2f616aa31336 --- /dev/null +++ b/src/test/ui/generator/partial-drop.rs @@ -0,0 +1,21 @@ +// check-pass + +#![feature(negative_impls, generators)] + +struct Foo; +impl !Send for Foo {} + +struct Bar { + foo: Foo, + x: i32, +} + +fn main() { + assert_send(|| { + let guard = Bar { foo: Foo, x: 42 }; + drop(guard.foo); + yield; + }) +} + +fn assert_send(_: T) {} From f5f98d7ee43ae591afffffc34fc2efab48eef785 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 13 Dec 2021 10:47:28 -0800 Subject: [PATCH 24/69] Refactor drop_ranges Splits drop_ranges into drop_ranges::record_consumed_borrow, drop_ranges::cfg_build, and drop_ranges::cfg_propagate. The top level drop_ranges module has an entry point that does all the coordination of the other three phases, using code original in generator_interior. --- .../src/check/generator_interior.rs | 50 +-- .../check/generator_interior/drop_ranges.rs | 399 ++---------------- .../drop_ranges/cfg_build.rs | 169 ++++++++ .../drop_ranges/cfg_propagate.rs | 67 +++ .../drop_ranges/cfg_visualize.rs | 68 +++ .../drop_ranges/record_consumed_borrow.rs | 112 +++++ 6 files changed, 455 insertions(+), 410 deletions(-) create mode 100644 compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs create mode 100644 compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs create mode 100644 compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs create mode 100644 compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 68269f24e9d33..56b6dd9a28446 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,9 +3,8 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. -use self::drop_ranges::{DropRangeVisitor, DropRanges, ExprUseDelegate}; +use self::drop_ranges::DropRanges; use super::FnCtxt; -use crate::expr_use_visitor::ExprUseVisitor; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -187,42 +186,17 @@ pub fn resolve_interior<'a, 'tcx>( kind: hir::GeneratorKind, ) { let body = fcx.tcx.hir().body(body_id); - - let mut visitor = { - let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx.hir()); - - // Run ExprUseVisitor to find where values are consumed. - ExprUseVisitor::new( - &mut expr_use_visitor, - &fcx.infcx, - def_id.expect_local(), - fcx.param_env, - &fcx.typeck_results.borrow(), - ) - .consume_body(body); - - let region_scope_tree = fcx.tcx.region_scope_tree(def_id); - let mut drop_range_visitor = DropRangeVisitor::from_uses( - expr_use_visitor, - region_scope_tree.body_expr_count(body.id()).unwrap_or(0), - ); - intravisit::walk_body(&mut drop_range_visitor, body); - - let mut drop_ranges = drop_range_visitor.into_drop_ranges(); - drop_ranges.propagate_to_fixpoint(); - - InteriorVisitor { - fcx, - types: FxIndexSet::default(), - region_scope_tree, - expr_count: 0, - kind, - prev_unresolved_span: None, - guard_bindings: <_>::default(), - guard_bindings_set: <_>::default(), - linted_values: <_>::default(), - drop_ranges: drop_ranges, - } + let mut visitor = InteriorVisitor { + fcx, + types: FxIndexSet::default(), + region_scope_tree: fcx.tcx.region_scope_tree(def_id), + expr_count: 0, + kind, + prev_unresolved_span: None, + guard_bindings: <_>::default(), + guard_bindings_set: <_>::default(), + linted_values: <_>::default(), + drop_ranges: drop_ranges::compute_drop_ranges(fcx, def_id, body), }; intravisit::walk_body(&mut visitor, body); diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 7b76ff5e02bf7..d8bda36b14fe8 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -12,180 +12,42 @@ //! The end result is a data structure that maps the post-order index of each node in the HIR tree //! to a set of values that are known to be dropped at that location. -use std::collections::BTreeMap; -use std::fmt::Debug; -use std::mem::swap; - -use hir::intravisit::{self, NestedVisitorMap, Visitor}; -use hir::{Expr, ExprKind, Guard, HirId, HirIdMap, HirIdSet, Node}; -use rustc_graphviz as dot; +use self::cfg_build::DropRangeVisitor; +use self::record_consumed_borrow::ExprUseDelegate; +use crate::check::FnCtxt; +use hir::def_id::DefId; +use hir::{Body, HirId, HirIdMap, Node, intravisit}; use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; use rustc_middle::hir::map::Map; -use rustc_middle::hir::place::{Place, PlaceBase}; -use rustc_middle::ty; - -use crate::expr_use_visitor; - -/// Works with ExprUseVisitor to find interesting values for the drop range analysis. -/// -/// Interesting values are those that are either dropped or borrowed. For dropped values, we also -/// record the parent expression, which is the point where the drop actually takes place. -pub struct ExprUseDelegate<'tcx> { - hir: Map<'tcx>, - /// Records the variables/expressions that are dropped by a given expression. - /// - /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables - /// or values that are consumed by that expression. - /// - /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is - /// not considered a drop of `x`, although it would be a drop of `x.y`. - consumed_places: HirIdMap, - /// A set of hir-ids of values or variables that are borrowed at some point within the body. - borrowed_places: HirIdSet, -} - -impl<'tcx> ExprUseDelegate<'tcx> { - pub fn new(hir: Map<'tcx>) -> Self { - Self { hir, consumed_places: <_>::default(), borrowed_places: <_>::default() } - } - - fn mark_consumed(&mut self, consumer: HirId, target: HirId) { - if !self.consumed_places.contains_key(&consumer) { - self.consumed_places.insert(consumer, <_>::default()); - } - self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); - } -} - -impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { - fn consume( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - diag_expr_id: hir::HirId, - ) { - let parent = match self.hir.find_parent_node(place_with_id.hir_id) { - Some(parent) => parent, - None => place_with_id.hir_id, - }; - debug!( - "consume {:?}; diag_expr_id={:?}, using parent {:?}", - place_with_id, diag_expr_id, parent - ); - self.mark_consumed(parent, place_with_id.hir_id); - place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); - } - - fn borrow( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - _bk: rustc_middle::ty::BorrowKind, - ) { - place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); - } - - fn mutate( - &mut self, - _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - ) { - } - - fn fake_read( - &mut self, - _place: expr_use_visitor::Place<'tcx>, - _cause: rustc_middle::mir::FakeReadCause, - _diag_expr_id: hir::HirId, - ) { - } -} - -/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to -/// track for a value in the drop range analysis. -fn place_hir_id(place: &Place<'_>) -> Option { - match place.base { - PlaceBase::Rvalue | PlaceBase::StaticItem => None, - PlaceBase::Local(hir_id) - | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), - } -} - -/// This struct is used to gather the information for `DropRanges` to determine the regions of the -/// HIR tree for which a value is dropped. -/// -/// We are interested in points where a variables is dropped or initialized, and the control flow -/// of the code. We identify locations in code by their post-order traversal index, so it is -/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. -pub struct DropRangeVisitor<'tcx> { - hir: Map<'tcx>, - /// Maps a HirId to a set of HirIds that are dropped by that node. - /// - /// See also the more detailed comment on `ExprUseDelegate.consumed_places`. - consumed_places: HirIdMap, - borrowed_places: HirIdSet, - drop_ranges: DropRanges, - expr_count: usize, -} +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::mem::swap; -impl<'tcx> DropRangeVisitor<'tcx> { - pub fn from_uses(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { - debug!("consumed_places: {:?}", uses.consumed_places); - let drop_ranges = DropRanges::new( - uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), - &uses.hir, - num_exprs, - ); - Self { - hir: uses.hir, - consumed_places: uses.consumed_places, - borrowed_places: uses.borrowed_places, - drop_ranges, - expr_count: 0, - } - } +mod cfg_build; +mod record_consumed_borrow; +mod cfg_propagate; +mod cfg_visualize; - pub fn into_drop_ranges(self) -> DropRanges { - self.drop_ranges - } +pub fn compute_drop_ranges<'a, 'tcx>( + fcx: &'a FnCtxt<'a, 'tcx>, + def_id: DefId, + body: &'tcx Body<'tcx>, +) -> DropRanges { + let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx.hir()); + expr_use_visitor.consume_body(fcx, def_id, body); - fn record_drop(&mut self, hir_id: HirId) { - if self.borrowed_places.contains(&hir_id) { - debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); - } else { - debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - let count = self.expr_count; - self.drop_ranges.drop_at(hir_id, count); - } - } + let mut drop_range_visitor = DropRangeVisitor::from_uses( + expr_use_visitor, + fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0), + ); + intravisit::walk_body(&mut drop_range_visitor, body); - /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all - /// expressions. This method consumes a little deeper into the expression when needed. - fn consume_expr(&mut self, expr: &hir::Expr<'_>) { - debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); - let places = self - .consumed_places - .get(&expr.hir_id) - .map_or(vec![], |places| places.iter().cloned().collect()); - for place in places { - for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); - } - } + let mut drop_ranges = drop_range_visitor.into_drop_ranges(); + drop_ranges.propagate_to_fixpoint(); - fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { - if let ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) = expr.kind - { - let location = self.expr_count; - debug!("reinitializing {:?} at {}", hir_id, location); - self.drop_ranges.reinit_at(*hir_id, location); - } else { - debug!("reinitializing {:?} is not supported", expr); - } - } + drop_ranges } /// Applies `f` to consumable portion of a HIR node. @@ -206,92 +68,6 @@ fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(H } } -impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { - type Map = intravisit::ErasedMap<'tcx>; - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - let mut reinit = None; - match expr.kind { - ExprKind::If(test, if_true, if_false) => { - self.visit_expr(test); - - let fork = self.expr_count; - - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - self.visit_expr(if_true); - let true_end = self.expr_count; - - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - if let Some(if_false) = if_false { - self.visit_expr(if_false); - } - - self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); - } - ExprKind::Assign(lhs, rhs, _) => { - self.visit_expr(lhs); - self.visit_expr(rhs); - - reinit = Some(lhs); - } - ExprKind::Loop(body, ..) => { - let loop_begin = self.expr_count + 1; - self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_count, loop_begin); - } - ExprKind::Match(scrutinee, arms, ..) => { - self.visit_expr(scrutinee); - - let fork = self.expr_count; - let arm_end_ids = arms - .iter() - .map(|hir::Arm { pat, body, guard, .. }| { - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - self.visit_pat(pat); - match guard { - Some(Guard::If(expr)) => self.visit_expr(expr), - Some(Guard::IfLet(pat, expr)) => { - self.visit_pat(pat); - self.visit_expr(expr); - } - None => (), - } - self.visit_expr(body); - self.expr_count - }) - .collect::>(); - arm_end_ids.into_iter().for_each(|arm_end| { - self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) - }); - } - ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) - | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { - self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); - } - - _ => intravisit::walk_expr(self, expr), - } - - self.expr_count += 1; - self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); - self.consume_expr(expr); - if let Some(expr) = reinit { - self.reinit_expr(expr); - } - } - - fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { - intravisit::walk_pat(self, pat); - - // Increment expr_count here to match what InteriorVisitor expects. - self.expr_count += 1; - } -} - rustc_index::newtype_index! { pub struct PostOrderId { DEBUG_FORMAT = "id({})", @@ -426,127 +202,6 @@ impl DropRanges { self.node_mut(location.into()).reinits.push(value); } - pub fn propagate_to_fixpoint(&mut self) { - trace!("before fixpoint: {:#?}", self); - self.process_deferred_edges(); - let preds = self.compute_predecessors(); - - trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); - - let mut propagate = || { - let mut changed = false; - for id in self.nodes.indices() { - let old_state = self.nodes[id].drop_state.clone(); - let mut new_state = if id.index() == 0 { - BitSet::new_empty(self.num_values()) - } else { - // If we are not the start node and we have no predecessors, treat - // everything as dropped because there's no way to get here anyway. - BitSet::new_filled(self.num_values()) - }; - - for pred in &preds[id] { - let state = &self.nodes[*pred].drop_state; - new_state.intersect(state); - } - - for drop in &self.nodes[id].drops { - new_state.insert(*drop); - } - - for reinit in &self.nodes[id].reinits { - new_state.remove(*reinit); - } - - changed |= old_state != new_state; - self.nodes[id].drop_state = new_state; - } - - changed - }; - - while propagate() { - trace!("drop_state changed, re-running propagation"); - } - - trace!("after fixpoint: {:#?}", self); - } - - fn compute_predecessors(&self) -> IndexVec> { - let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len()); - for (id, node) in self.nodes.iter_enumerated() { - if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 { - preds[id + 1].push(id); - } else { - for succ in &node.successors { - preds[*succ].push(id); - } - } - } - preds - } -} - -impl<'a> dot::GraphWalk<'a> for DropRanges { - type Node = PostOrderId; - - type Edge = (PostOrderId, PostOrderId); - - fn nodes(&'a self) -> dot::Nodes<'a, Self::Node> { - self.nodes.iter_enumerated().map(|(i, _)| i).collect() - } - - fn edges(&'a self) -> dot::Edges<'a, Self::Edge> { - self.nodes - .iter_enumerated() - .flat_map(|(i, node)| { - if node.successors.len() == 0 { - vec![(i, i + 1)] - } else { - node.successors.iter().map(move |&s| (i, s)).collect() - } - }) - .collect() - } - - fn source(&'a self, edge: &Self::Edge) -> Self::Node { - edge.0 - } - - fn target(&'a self, edge: &Self::Edge) -> Self::Node { - edge.1 - } -} - -impl<'a> dot::Labeller<'a> for DropRanges { - type Node = PostOrderId; - - type Edge = (PostOrderId, PostOrderId); - - fn graph_id(&'a self) -> dot::Id<'a> { - dot::Id::new("drop_ranges").unwrap() - } - - fn node_id(&'a self, n: &Self::Node) -> dot::Id<'a> { - dot::Id::new(format!("id{}", n.index())).unwrap() - } - - fn node_label(&'a self, n: &Self::Node) -> dot::LabelText<'a> { - dot::LabelText::LabelStr( - format!( - "{:?}, local_id: {}", - n, - self.post_order_map - .iter() - .find(|(_hir_id, &post_order_id)| post_order_id == n.index()) - .map_or("".into(), |(hir_id, _)| format!( - "{}", - hir_id.local_id.index() - )) - ) - .into(), - ) - } } #[derive(Debug)] diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs new file mode 100644 index 0000000000000..594054fde9ae7 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -0,0 +1,169 @@ +use super::{for_each_consumable, record_consumed_borrow::ExprUseDelegate, DropRanges}; +use hir::{ + intravisit::{self, NestedVisitorMap, Visitor}, + Expr, ExprKind, Guard, HirId, HirIdMap, HirIdSet, +}; +use rustc_hir as hir; +use rustc_middle::hir::map::Map; + +/// This struct is used to gather the information for `DropRanges` to determine the regions of the +/// HIR tree for which a value is dropped. +/// +/// We are interested in points where a variables is dropped or initialized, and the control flow +/// of the code. We identify locations in code by their post-order traversal index, so it is +/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. +pub struct DropRangeVisitor<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + /// + /// See also the more detailed comment on `ExprUseDelegate.consumed_places`. + consumed_places: HirIdMap, + borrowed_places: HirIdSet, + drop_ranges: DropRanges, + expr_count: usize, +} + +impl<'tcx> DropRangeVisitor<'tcx> { + pub fn from_uses(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { + debug!("consumed_places: {:?}", uses.consumed_places); + let drop_ranges = DropRanges::new( + uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), + &uses.hir, + num_exprs, + ); + Self { + hir: uses.hir, + consumed_places: uses.consumed_places, + borrowed_places: uses.borrowed_places, + drop_ranges, + expr_count: 0, + } + } + + pub fn into_drop_ranges(self) -> DropRanges { + self.drop_ranges + } + + fn record_drop(&mut self, hir_id: HirId) { + if self.borrowed_places.contains(&hir_id) { + debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); + } else { + debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); + let count = self.expr_count; + self.drop_ranges.drop_at(hir_id, count); + } + } + + /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all + /// expressions. This method consumes a little deeper into the expression when needed. + fn consume_expr(&mut self, expr: &hir::Expr<'_>) { + debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); + let places = self + .consumed_places + .get(&expr.hir_id) + .map_or(vec![], |places| places.iter().cloned().collect()); + for place in places { + for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); + } + } + + fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { + if let ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) = expr.kind + { + let location = self.expr_count; + debug!("reinitializing {:?} at {}", hir_id, location); + self.drop_ranges.reinit_at(*hir_id, location); + } else { + debug!("reinitializing {:?} is not supported", expr); + } + } +} + +impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { + type Map = intravisit::ErasedMap<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + let mut reinit = None; + match expr.kind { + ExprKind::If(test, if_true, if_false) => { + self.visit_expr(test); + + let fork = self.expr_count; + + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.visit_expr(if_true); + let true_end = self.expr_count; + + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + if let Some(if_false) = if_false { + self.visit_expr(if_false); + } + + self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); + } + ExprKind::Assign(lhs, rhs, _) => { + self.visit_expr(lhs); + self.visit_expr(rhs); + + reinit = Some(lhs); + } + ExprKind::Loop(body, ..) => { + let loop_begin = self.expr_count + 1; + self.visit_block(body); + self.drop_ranges.add_control_edge(self.expr_count, loop_begin); + } + ExprKind::Match(scrutinee, arms, ..) => { + self.visit_expr(scrutinee); + + let fork = self.expr_count; + let arm_end_ids = arms + .iter() + .map(|hir::Arm { pat, body, guard, .. }| { + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.visit_pat(pat); + match guard { + Some(Guard::If(expr)) => self.visit_expr(expr), + Some(Guard::IfLet(pat, expr)) => { + self.visit_pat(pat); + self.visit_expr(expr); + } + None => (), + } + self.visit_expr(body); + self.expr_count + }) + .collect::>(); + arm_end_ids.into_iter().for_each(|arm_end| { + self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) + }); + } + ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) + | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { + self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); + } + + _ => intravisit::walk_expr(self, expr), + } + + self.expr_count += 1; + self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); + self.consume_expr(expr); + if let Some(expr) = reinit { + self.reinit_expr(expr); + } + } + + fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { + intravisit::walk_pat(self, pat); + + // Increment expr_count here to match what InteriorVisitor expects. + self.expr_count += 1; + } +} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs new file mode 100644 index 0000000000000..ea7b9106b9a80 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs @@ -0,0 +1,67 @@ +use std::collections::BTreeMap; + +use rustc_index::{bit_set::BitSet, vec::IndexVec}; + +use super::{DropRanges, PostOrderId}; + +impl DropRanges { + pub fn propagate_to_fixpoint(&mut self) { + trace!("before fixpoint: {:#?}", self); + self.process_deferred_edges(); + let preds = self.compute_predecessors(); + + trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); + + let mut propagate = || { + let mut changed = false; + for id in self.nodes.indices() { + let old_state = self.nodes[id].drop_state.clone(); + let mut new_state = if id.index() == 0 { + BitSet::new_empty(self.num_values()) + } else { + // If we are not the start node and we have no predecessors, treat + // everything as dropped because there's no way to get here anyway. + BitSet::new_filled(self.num_values()) + }; + + for pred in &preds[id] { + let state = &self.nodes[*pred].drop_state; + new_state.intersect(state); + } + + for drop in &self.nodes[id].drops { + new_state.insert(*drop); + } + + for reinit in &self.nodes[id].reinits { + new_state.remove(*reinit); + } + + changed |= old_state != new_state; + self.nodes[id].drop_state = new_state; + } + + changed + }; + + while propagate() { + trace!("drop_state changed, re-running propagation"); + } + + trace!("after fixpoint: {:#?}", self); + } + + fn compute_predecessors(&self) -> IndexVec> { + let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len()); + for (id, node) in self.nodes.iter_enumerated() { + if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 { + preds[id + 1].push(id); + } else { + for succ in &node.successors { + preds[*succ].push(id); + } + } + } + preds + } +} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs new file mode 100644 index 0000000000000..ebbbec1c472b6 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs @@ -0,0 +1,68 @@ +//! Implementation of GraphWalk for DropRanges so we can visualize the control +//! flow graph when needed for debugging. + +use rustc_graphviz as dot; + +use super::{DropRanges, PostOrderId}; + +impl<'a> dot::GraphWalk<'a> for DropRanges { + type Node = PostOrderId; + + type Edge = (PostOrderId, PostOrderId); + + fn nodes(&'a self) -> dot::Nodes<'a, Self::Node> { + self.nodes.iter_enumerated().map(|(i, _)| i).collect() + } + + fn edges(&'a self) -> dot::Edges<'a, Self::Edge> { + self.nodes + .iter_enumerated() + .flat_map(|(i, node)| { + if node.successors.len() == 0 { + vec![(i, i + 1)] + } else { + node.successors.iter().map(move |&s| (i, s)).collect() + } + }) + .collect() + } + + fn source(&'a self, edge: &Self::Edge) -> Self::Node { + edge.0 + } + + fn target(&'a self, edge: &Self::Edge) -> Self::Node { + edge.1 + } +} + +impl<'a> dot::Labeller<'a> for DropRanges { + type Node = PostOrderId; + + type Edge = (PostOrderId, PostOrderId); + + fn graph_id(&'a self) -> dot::Id<'a> { + dot::Id::new("drop_ranges").unwrap() + } + + fn node_id(&'a self, n: &Self::Node) -> dot::Id<'a> { + dot::Id::new(format!("id{}", n.index())).unwrap() + } + + fn node_label(&'a self, n: &Self::Node) -> dot::LabelText<'a> { + dot::LabelText::LabelStr( + format!( + "{:?}, local_id: {}", + n, + self.post_order_map + .iter() + .find(|(_hir_id, &post_order_id)| post_order_id == n.index()) + .map_or("".into(), |(hir_id, _)| format!( + "{}", + hir_id.local_id.index() + )) + ) + .into(), + ) + } +} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs new file mode 100644 index 0000000000000..93bb58cd8a099 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -0,0 +1,112 @@ +use crate::{ + check::FnCtxt, + expr_use_visitor::{self, ExprUseVisitor}, +}; +use hir::{HirId, HirIdMap, HirIdSet, Body, def_id::DefId}; +use rustc_hir as hir; +use rustc_middle::hir::{ + map::Map, + place::{Place, PlaceBase}, +}; +use rustc_middle::ty; + +/// Works with ExprUseVisitor to find interesting values for the drop range analysis. +/// +/// Interesting values are those that are either dropped or borrowed. For dropped values, we also +/// record the parent expression, which is the point where the drop actually takes place. +pub struct ExprUseDelegate<'tcx> { + pub(super) hir: Map<'tcx>, + /// Records the variables/expressions that are dropped by a given expression. + /// + /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables + /// or values that are consumed by that expression. + /// + /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is + /// not considered a drop of `x`, although it would be a drop of `x.y`. + pub(super) consumed_places: HirIdMap, + /// A set of hir-ids of values or variables that are borrowed at some point within the body. + pub(super) borrowed_places: HirIdSet, +} + +impl<'tcx> ExprUseDelegate<'tcx> { + pub fn new(hir: Map<'tcx>) -> Self { + Self { hir, consumed_places: <_>::default(), borrowed_places: <_>::default() } + } + + pub fn consume_body( + &mut self, + fcx: &'_ FnCtxt<'_, 'tcx>, + def_id: DefId, + body: &'tcx Body<'tcx>, + ) { + // Run ExprUseVisitor to find where values are consumed. + ExprUseVisitor::new( + self, + &fcx.infcx, + def_id.expect_local(), + fcx.param_env, + &fcx.typeck_results.borrow(), + ) + .consume_body(body); + } + + fn mark_consumed(&mut self, consumer: HirId, target: HirId) { + if !self.consumed_places.contains_key(&consumer) { + self.consumed_places.insert(consumer, <_>::default()); + } + self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); + } +} + +impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { + fn consume( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + diag_expr_id: HirId, + ) { + let parent = match self.hir.find_parent_node(place_with_id.hir_id) { + Some(parent) => parent, + None => place_with_id.hir_id, + }; + debug!( + "consume {:?}; diag_expr_id={:?}, using parent {:?}", + place_with_id, diag_expr_id, parent + ); + self.mark_consumed(parent, place_with_id.hir_id); + place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); + } + + fn borrow( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: HirId, + _bk: rustc_middle::ty::BorrowKind, + ) { + place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); + } + + fn mutate( + &mut self, + _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: HirId, + ) { + } + + fn fake_read( + &mut self, + _place: expr_use_visitor::Place<'tcx>, + _cause: rustc_middle::mir::FakeReadCause, + _diag_expr_id: HirId, + ) { + } +} + +/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to +/// track for a value in the drop range analysis. +fn place_hir_id(place: &Place<'_>) -> Option { + match place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => None, + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), + } +} From 9347bf498a9456ab14ec9d9efee25451e80a8642 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 13 Dec 2021 15:01:26 -0800 Subject: [PATCH 25/69] Additional cleanup This cleans up the refactoring from the previous patch and cleans things up a bit. Each module has a clear entry point and everything else is private. --- .../check/generator_interior/drop_ranges.rs | 94 ++------------- .../drop_ranges/cfg_build.rs | 114 ++++++++++++++---- .../drop_ranges/cfg_propagate.rs | 21 +++- .../drop_ranges/record_consumed_borrow.rs | 52 +++++--- 4 files changed, 151 insertions(+), 130 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index d8bda36b14fe8..b200320b8d3b7 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -12,39 +12,33 @@ //! The end result is a data structure that maps the post-order index of each node in the HIR tree //! to a set of values that are known to be dropped at that location. -use self::cfg_build::DropRangeVisitor; -use self::record_consumed_borrow::ExprUseDelegate; +use self::cfg_build::build_control_flow_graph; +use self::record_consumed_borrow::find_consumed_and_borrowed; use crate::check::FnCtxt; use hir::def_id::DefId; -use hir::{Body, HirId, HirIdMap, Node, intravisit}; +use hir::{Body, HirId, HirIdMap, Node}; use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; -use rustc_middle::hir::map::Map; use std::collections::BTreeMap; use std::fmt::Debug; -use std::mem::swap; mod cfg_build; -mod record_consumed_borrow; mod cfg_propagate; mod cfg_visualize; +mod record_consumed_borrow; pub fn compute_drop_ranges<'a, 'tcx>( fcx: &'a FnCtxt<'a, 'tcx>, def_id: DefId, body: &'tcx Body<'tcx>, ) -> DropRanges { - let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx.hir()); - expr_use_visitor.consume_body(fcx, def_id, body); + let consumed_borrowed_places = find_consumed_and_borrowed(fcx, def_id, body); - let mut drop_range_visitor = DropRangeVisitor::from_uses( - expr_use_visitor, - fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0), - ); - intravisit::walk_body(&mut drop_range_visitor, body); + let num_exprs = fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0); + let mut drop_ranges = + build_control_flow_graph(fcx.tcx.hir(), consumed_borrowed_places, body, num_exprs); - let mut drop_ranges = drop_range_visitor.into_drop_ranges(); drop_ranges.propagate_to_fixpoint(); drop_ranges @@ -105,31 +99,6 @@ impl Debug for DropRanges { /// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely /// dropped at the point of the node identified by post_order_id. impl DropRanges { - pub fn new(hir_ids: impl Iterator, hir: &Map<'_>, num_exprs: usize) -> Self { - let mut hir_id_map = HirIdMap::::default(); - let mut next = <_>::from(0u32); - for hir_id in hir_ids { - for_each_consumable(hir_id, hir.find(hir_id), |hir_id| { - if !hir_id_map.contains_key(&hir_id) { - hir_id_map.insert(hir_id, next); - next = <_>::from(next.index() + 1); - } - }); - } - debug!("hir_id_map: {:?}", hir_id_map); - let num_values = hir_id_map.len(); - Self { - hir_id_map, - nodes: IndexVec::from_fn_n(|_| NodeInfo::new(num_values), num_exprs + 1), - deferred_edges: <_>::default(), - post_order_map: <_>::default(), - } - } - - fn hidx(&self, hir_id: HirId) -> HirIdIndex { - *self.hir_id_map.get(&hir_id).unwrap() - } - pub fn is_dropped_at(&mut self, hir_id: HirId, location: usize) -> bool { self.hir_id_map .get(&hir_id) @@ -142,13 +111,6 @@ impl DropRanges { self.hir_id_map.len() } - /// Adds an entry in the mapping from HirIds to PostOrderIds - /// - /// Needed so that `add_control_edge_hir_id` can work. - pub fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: usize) { - self.post_order_map.insert(hir_id, post_order_id); - } - /// Returns a reference to the NodeInfo for a node, panicking if it does not exist fn expect_node(&self, id: PostOrderId) -> &NodeInfo { &self.nodes[id] @@ -160,48 +122,10 @@ impl DropRanges { &mut self.nodes[id] } - pub fn add_control_edge(&mut self, from: usize, to: usize) { + fn add_control_edge(&mut self, from: usize, to: usize) { trace!("adding control edge from {} to {}", from, to); self.node_mut(from.into()).successors.push(to.into()); } - - /// Like add_control_edge, but uses a hir_id as the target. - /// - /// This can be used for branches where we do not know the PostOrderId of the target yet, - /// such as when handling `break` or `continue`. - pub fn add_control_edge_hir_id(&mut self, from: usize, to: HirId) { - self.deferred_edges.push((from, to)); - } - - /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them. - /// - /// Should be called after visiting the HIR but before solving the control flow, otherwise some - /// edges will be missed. - fn process_deferred_edges(&mut self) { - let mut edges = vec![]; - swap(&mut edges, &mut self.deferred_edges); - edges.into_iter().for_each(|(from, to)| { - let to = *self.post_order_map.get(&to).expect("Expression ID not found"); - trace!("Adding deferred edge from {} to {}", from, to); - self.add_control_edge(from, to) - }); - } - - pub fn drop_at(&mut self, value: HirId, location: usize) { - let value = self.hidx(value); - self.node_mut(location.into()).drops.push(value); - } - - pub fn reinit_at(&mut self, value: HirId, location: usize) { - let value = match self.hir_id_map.get(&value) { - Some(value) => *value, - // If there's no value, this is never consumed and therefore is never dropped. We can - // ignore this. - None => return, - }; - self.node_mut(location.into()).reinits.push(value); - } - } #[derive(Debug)] diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index 594054fde9ae7..e1f1b44283bbe 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -1,51 +1,57 @@ -use super::{for_each_consumable, record_consumed_borrow::ExprUseDelegate, DropRanges}; +use super::{ + for_each_consumable, record_consumed_borrow::ConsumedAndBorrowedPlaces, DropRanges, HirIdIndex, + NodeInfo, +}; use hir::{ intravisit::{self, NestedVisitorMap, Visitor}, - Expr, ExprKind, Guard, HirId, HirIdMap, HirIdSet, + Body, Expr, ExprKind, Guard, HirId, HirIdMap, }; use rustc_hir as hir; +use rustc_index::vec::IndexVec; use rustc_middle::hir::map::Map; +/// Traverses the body to find the control flow graph and locations for the +/// relevant places are dropped or reinitialized. +/// +/// The resulting structure still needs to be iterated to a fixed point, which +/// can be done with propagate_to_fixpoint in cfg_propagate. +pub fn build_control_flow_graph<'tcx>( + hir: Map<'tcx>, + consumed_borrowed_places: ConsumedAndBorrowedPlaces, + body: &'tcx Body<'tcx>, + num_exprs: usize, +) -> DropRanges { + let mut drop_range_visitor = DropRangeVisitor::new(hir, consumed_borrowed_places, num_exprs); + intravisit::walk_body(&mut drop_range_visitor, body); + drop_range_visitor.drop_ranges +} + /// This struct is used to gather the information for `DropRanges` to determine the regions of the /// HIR tree for which a value is dropped. /// /// We are interested in points where a variables is dropped or initialized, and the control flow /// of the code. We identify locations in code by their post-order traversal index, so it is /// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. -pub struct DropRangeVisitor<'tcx> { +struct DropRangeVisitor<'tcx> { hir: Map<'tcx>, - /// Maps a HirId to a set of HirIds that are dropped by that node. - /// - /// See also the more detailed comment on `ExprUseDelegate.consumed_places`. - consumed_places: HirIdMap, - borrowed_places: HirIdSet, + places: ConsumedAndBorrowedPlaces, drop_ranges: DropRanges, expr_count: usize, } impl<'tcx> DropRangeVisitor<'tcx> { - pub fn from_uses(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { - debug!("consumed_places: {:?}", uses.consumed_places); + fn new(hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, num_exprs: usize) -> Self { + debug!("consumed_places: {:?}", places.consumed); let drop_ranges = DropRanges::new( - uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), - &uses.hir, + places.consumed.iter().flat_map(|(_, places)| places.iter().copied()), + hir, num_exprs, ); - Self { - hir: uses.hir, - consumed_places: uses.consumed_places, - borrowed_places: uses.borrowed_places, - drop_ranges, - expr_count: 0, - } - } - - pub fn into_drop_ranges(self) -> DropRanges { - self.drop_ranges + Self { hir, places, drop_ranges, expr_count: 0 } } fn record_drop(&mut self, hir_id: HirId) { - if self.borrowed_places.contains(&hir_id) { + if self.places.borrowed.contains(&hir_id) { debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); @@ -59,7 +65,8 @@ impl<'tcx> DropRangeVisitor<'tcx> { fn consume_expr(&mut self, expr: &hir::Expr<'_>) { debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); let places = self - .consumed_places + .places + .consumed .get(&expr.hir_id) .map_or(vec![], |places| places.iter().cloned().collect()); for place in places { @@ -167,3 +174,60 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.expr_count += 1; } } + +impl DropRanges { + fn new(hir_ids: impl Iterator, hir: Map<'_>, num_exprs: usize) -> Self { + let mut hir_id_map = HirIdMap::::default(); + let mut next = <_>::from(0u32); + for hir_id in hir_ids { + for_each_consumable(hir_id, hir.find(hir_id), |hir_id| { + if !hir_id_map.contains_key(&hir_id) { + hir_id_map.insert(hir_id, next); + next = <_>::from(next.index() + 1); + } + }); + } + debug!("hir_id_map: {:?}", hir_id_map); + let num_values = hir_id_map.len(); + Self { + hir_id_map, + nodes: IndexVec::from_fn_n(|_| NodeInfo::new(num_values), num_exprs + 1), + deferred_edges: <_>::default(), + post_order_map: <_>::default(), + } + } + + fn hidx(&self, hir_id: HirId) -> HirIdIndex { + *self.hir_id_map.get(&hir_id).unwrap() + } + + /// Adds an entry in the mapping from HirIds to PostOrderIds + /// + /// Needed so that `add_control_edge_hir_id` can work. + fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: usize) { + self.post_order_map.insert(hir_id, post_order_id); + } + + /// Like add_control_edge, but uses a hir_id as the target. + /// + /// This can be used for branches where we do not know the PostOrderId of the target yet, + /// such as when handling `break` or `continue`. + fn add_control_edge_hir_id(&mut self, from: usize, to: HirId) { + self.deferred_edges.push((from, to)); + } + + fn drop_at(&mut self, value: HirId, location: usize) { + let value = self.hidx(value); + self.node_mut(location.into()).drops.push(value); + } + + fn reinit_at(&mut self, value: HirId, location: usize) { + let value = match self.hir_id_map.get(&value) { + Some(value) => *value, + // If there's no value, this is never consumed and therefore is never dropped. We can + // ignore this. + None => return, + }; + self.node_mut(location.into()).reinits.push(value); + } +} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs index ea7b9106b9a80..74ce762864e0f 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs @@ -1,8 +1,7 @@ -use std::collections::BTreeMap; - -use rustc_index::{bit_set::BitSet, vec::IndexVec}; - use super::{DropRanges, PostOrderId}; +use rustc_index::{bit_set::BitSet, vec::IndexVec}; +use std::collections::BTreeMap; +use std::mem::swap; impl DropRanges { pub fn propagate_to_fixpoint(&mut self) { @@ -64,4 +63,18 @@ impl DropRanges { } preds } + + /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them. + /// + /// Should be called after visiting the HIR but before solving the control flow, otherwise some + /// edges will be missed. + fn process_deferred_edges(&mut self) { + let mut edges = vec![]; + swap(&mut edges, &mut self.deferred_edges); + edges.into_iter().for_each(|(from, to)| { + let to = *self.post_order_map.get(&to).expect("Expression ID not found"); + trace!("Adding deferred edge from {} to {}", from, to); + self.add_control_edge(from, to) + }); + } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs index 93bb58cd8a099..e8cee21168ab3 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -2,7 +2,7 @@ use crate::{ check::FnCtxt, expr_use_visitor::{self, ExprUseVisitor}, }; -use hir::{HirId, HirIdMap, HirIdSet, Body, def_id::DefId}; +use hir::{def_id::DefId, Body, HirId, HirIdMap, HirIdSet}; use rustc_hir as hir; use rustc_middle::hir::{ map::Map, @@ -10,12 +10,17 @@ use rustc_middle::hir::{ }; use rustc_middle::ty; -/// Works with ExprUseVisitor to find interesting values for the drop range analysis. -/// -/// Interesting values are those that are either dropped or borrowed. For dropped values, we also -/// record the parent expression, which is the point where the drop actually takes place. -pub struct ExprUseDelegate<'tcx> { - pub(super) hir: Map<'tcx>, +pub fn find_consumed_and_borrowed<'a, 'tcx>( + fcx: &'a FnCtxt<'a, 'tcx>, + def_id: DefId, + body: &'tcx Body<'tcx>, +) -> ConsumedAndBorrowedPlaces { + let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx.hir()); + expr_use_visitor.consume_body(fcx, def_id, body); + expr_use_visitor.places +} + +pub struct ConsumedAndBorrowedPlaces { /// Records the variables/expressions that are dropped by a given expression. /// /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables @@ -23,17 +28,32 @@ pub struct ExprUseDelegate<'tcx> { /// /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is /// not considered a drop of `x`, although it would be a drop of `x.y`. - pub(super) consumed_places: HirIdMap, + pub consumed: HirIdMap, /// A set of hir-ids of values or variables that are borrowed at some point within the body. - pub(super) borrowed_places: HirIdSet, + pub borrowed: HirIdSet, +} + +/// Works with ExprUseVisitor to find interesting values for the drop range analysis. +/// +/// Interesting values are those that are either dropped or borrowed. For dropped values, we also +/// record the parent expression, which is the point where the drop actually takes place. +struct ExprUseDelegate<'tcx> { + hir: Map<'tcx>, + places: ConsumedAndBorrowedPlaces, } impl<'tcx> ExprUseDelegate<'tcx> { - pub fn new(hir: Map<'tcx>) -> Self { - Self { hir, consumed_places: <_>::default(), borrowed_places: <_>::default() } + fn new(hir: Map<'tcx>) -> Self { + Self { + hir, + places: ConsumedAndBorrowedPlaces { + consumed: <_>::default(), + borrowed: <_>::default(), + }, + } } - pub fn consume_body( + fn consume_body( &mut self, fcx: &'_ FnCtxt<'_, 'tcx>, def_id: DefId, @@ -51,10 +71,10 @@ impl<'tcx> ExprUseDelegate<'tcx> { } fn mark_consumed(&mut self, consumer: HirId, target: HirId) { - if !self.consumed_places.contains_key(&consumer) { - self.consumed_places.insert(consumer, <_>::default()); + if !self.places.consumed.contains_key(&consumer) { + self.places.consumed.insert(consumer, <_>::default()); } - self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); + self.places.consumed.get_mut(&consumer).map(|places| places.insert(target)); } } @@ -82,7 +102,7 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { _diag_expr_id: HirId, _bk: rustc_middle::ty::BorrowKind, ) { - place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); + place_hir_id(&place_with_id.place).map(|place| self.places.borrowed.insert(place)); } fn mutate( From 6a28afb2a3ea199a86bfc98af02df444b40c44ca Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 13 Dec 2021 16:07:02 -0800 Subject: [PATCH 26/69] Fixing formatting --- .../drop_ranges/record_consumed_borrow.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs index e8cee21168ab3..36e843e7fd1f3 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -53,12 +53,7 @@ impl<'tcx> ExprUseDelegate<'tcx> { } } - fn consume_body( - &mut self, - fcx: &'_ FnCtxt<'_, 'tcx>, - def_id: DefId, - body: &'tcx Body<'tcx>, - ) { + fn consume_body(&mut self, fcx: &'_ FnCtxt<'_, 'tcx>, def_id: DefId, body: &'tcx Body<'tcx>) { // Run ExprUseVisitor to find where values are consumed. ExprUseVisitor::new( self, From 7d82e4f7642a3675e7dc87a483d79cf02681d930 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 15 Dec 2021 12:35:34 -0800 Subject: [PATCH 27/69] Update stderr files --- src/test/ui/async-await/unresolved_type_param.stderr | 8 ++++---- src/test/ui/lint/must_not_suspend/dedup.stderr | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/ui/async-await/unresolved_type_param.stderr b/src/test/ui/async-await/unresolved_type_param.stderr index 6a268bcda6297..d19a3226ef9a4 100644 --- a/src/test/ui/async-await/unresolved_type_param.stderr +++ b/src/test/ui/async-await/unresolved_type_param.stderr @@ -17,10 +17,10 @@ LL | bar().await; | ^^^ cannot infer type for type parameter `T` declared on the function `bar` | note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:5 + --> $DIR/unresolved_type_param.rs:9:10 | LL | bar().await; - | ^^^^^^^^^^^ + | ^^^^^^ error[E0698]: type inside `async fn` body must be known in this context --> $DIR/unresolved_type_param.rs:9:5 @@ -29,10 +29,10 @@ LL | bar().await; | ^^^ cannot infer type for type parameter `T` declared on the function `bar` | note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:5 + --> $DIR/unresolved_type_param.rs:9:10 | LL | bar().await; - | ^^^^^^^^^^^ + | ^^^^^^ error: aborting due to 3 previous errors diff --git a/src/test/ui/lint/must_not_suspend/dedup.stderr b/src/test/ui/lint/must_not_suspend/dedup.stderr index d15137474527e..13fa3ae3008d2 100644 --- a/src/test/ui/lint/must_not_suspend/dedup.stderr +++ b/src/test/ui/lint/must_not_suspend/dedup.stderr @@ -2,7 +2,7 @@ error: `No` held across a suspend point, but should not be --> $DIR/dedup.rs:16:13 | LL | wheeee(&No {}).await; - | --------^^^^^------- the value is held across this suspend point + | ^^^^^ ------ the value is held across this suspend point | note: the lint level is defined here --> $DIR/dedup.rs:3:9 From 2af02cf2c4061f517d5fc81591c9ae6b53225d24 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 15 Dec 2021 16:00:48 -0800 Subject: [PATCH 28/69] More comments and refactoring The refactoring mainly keeps the separation between the modules clearer. For example, process_deferred_edges function moved to cfg_build.rs since that is really part of building the CFG, not finding the fixpoint. Also, we use PostOrderId instead of usize in a lot more places now. --- .../check/generator_interior/drop_ranges.rs | 75 ++++++++++++----- .../drop_ranges/cfg_build.rs | 84 +++++++++++-------- .../drop_ranges/cfg_propagate.rs | 25 ++---- .../drop_ranges/cfg_visualize.rs | 8 +- 4 files changed, 117 insertions(+), 75 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index b200320b8d3b7..9fbefcfb088c3 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -41,7 +41,7 @@ pub fn compute_drop_ranges<'a, 'tcx>( drop_ranges.propagate_to_fixpoint(); - drop_ranges + DropRanges { hir_id_map: drop_ranges.hir_id_map, nodes: drop_ranges.nodes } } /// Applies `f` to consumable portion of a HIR node. @@ -77,12 +77,59 @@ rustc_index::newtype_index! { pub struct DropRanges { hir_id_map: HirIdMap, nodes: IndexVec, - deferred_edges: Vec<(usize, HirId)>, - // FIXME: This should only be used for loops and break/continue. - post_order_map: HirIdMap, } -impl Debug for DropRanges { +impl DropRanges { + pub fn is_dropped_at(&self, hir_id: HirId, location: usize) -> bool { + self.hir_id_map + .get(&hir_id) + .copied() + .map_or(false, |hir_id| self.expect_node(location.into()).drop_state.contains(hir_id)) + } + + /// Returns a reference to the NodeInfo for a node, panicking if it does not exist + fn expect_node(&self, id: PostOrderId) -> &NodeInfo { + &self.nodes[id] + } +} + +/// Tracks information needed to compute drop ranges. +struct DropRangesBuilder { + /// The core of DropRangesBuilder is a set of nodes, which each represent + /// one expression. We primarily refer to them by their index in a + /// post-order traversal of the HIR tree, since this is what + /// generator_interior uses to talk about yield positions. + /// + /// This IndexVec keeps the relevant details for each node. See the + /// NodeInfo struct for more details, but this information includes things + /// such as the set of control-flow successors, which variables are dropped + /// or reinitialized, and whether each variable has been inferred to be + /// known-dropped or potentially reintiialized at each point. + nodes: IndexVec, + /// We refer to values whose drop state we are tracking by the HirId of + /// where they are defined. Within a NodeInfo, however, we store the + /// drop-state in a bit vector indexed by a HirIdIndex + /// (see NodeInfo::drop_state). The hir_id_map field stores the mapping + /// from HirIds to the HirIdIndex that is used to represent that value in + /// bitvector. + hir_id_map: HirIdMap, + + /// When building the control flow graph, we don't always know the + /// post-order index of the target node at the point we encounter it. + /// For example, this happens with break and continue. In those cases, + /// we store a pair of the PostOrderId of the source and the HirId + /// of the target. Once we have gathered all of these edges, we make a + /// pass over the set of deferred edges (see process_deferred_edges in + /// cfg_build.rs), look up the PostOrderId for the target (since now the + /// post-order index for all nodes is known), and add missing control flow + /// edges. + deferred_edges: Vec<(PostOrderId, HirId)>, + /// This maps HirIds of expressions to their post-order index. It is + /// used in process_deferred_edges to correctly add back-edges. + post_order_map: HirIdMap, +} + +impl Debug for DropRangesBuilder { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DropRanges") .field("hir_id_map", &self.hir_id_map) @@ -98,32 +145,20 @@ impl Debug for DropRanges { /// by their index in the post-order traversal. At its core, DropRanges maps /// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely /// dropped at the point of the node identified by post_order_id. -impl DropRanges { - pub fn is_dropped_at(&mut self, hir_id: HirId, location: usize) -> bool { - self.hir_id_map - .get(&hir_id) - .copied() - .map_or(false, |hir_id| self.expect_node(location.into()).drop_state.contains(hir_id)) - } - +impl DropRangesBuilder { /// Returns the number of values (hir_ids) that are tracked fn num_values(&self) -> usize { self.hir_id_map.len() } - /// Returns a reference to the NodeInfo for a node, panicking if it does not exist - fn expect_node(&self, id: PostOrderId) -> &NodeInfo { - &self.nodes[id] - } - fn node_mut(&mut self, id: PostOrderId) -> &mut NodeInfo { let size = self.num_values(); self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); &mut self.nodes[id] } - fn add_control_edge(&mut self, from: usize, to: usize) { - trace!("adding control edge from {} to {}", from, to); + fn add_control_edge(&mut self, from: PostOrderId, to: PostOrderId) { + trace!("adding control edge from {:?} to {:?}", from, to); self.node_mut(from.into()).successors.push(to.into()); } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index e1f1b44283bbe..32f423f3bfeef 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -1,6 +1,6 @@ use super::{ - for_each_consumable, record_consumed_borrow::ConsumedAndBorrowedPlaces, DropRanges, HirIdIndex, - NodeInfo, + for_each_consumable, record_consumed_borrow::ConsumedAndBorrowedPlaces, DropRangesBuilder, + HirIdIndex, NodeInfo, PostOrderId, }; use hir::{ intravisit::{self, NestedVisitorMap, Visitor}, @@ -9,20 +9,24 @@ use hir::{ use rustc_hir as hir; use rustc_index::vec::IndexVec; use rustc_middle::hir::map::Map; +use std::mem::swap; /// Traverses the body to find the control flow graph and locations for the /// relevant places are dropped or reinitialized. /// /// The resulting structure still needs to be iterated to a fixed point, which /// can be done with propagate_to_fixpoint in cfg_propagate. -pub fn build_control_flow_graph<'tcx>( +pub(super) fn build_control_flow_graph<'tcx>( hir: Map<'tcx>, consumed_borrowed_places: ConsumedAndBorrowedPlaces, body: &'tcx Body<'tcx>, num_exprs: usize, -) -> DropRanges { +) -> DropRangesBuilder { let mut drop_range_visitor = DropRangeVisitor::new(hir, consumed_borrowed_places, num_exprs); intravisit::walk_body(&mut drop_range_visitor, body); + + drop_range_visitor.drop_ranges.process_deferred_edges(); + drop_range_visitor.drop_ranges } @@ -35,27 +39,27 @@ pub fn build_control_flow_graph<'tcx>( struct DropRangeVisitor<'tcx> { hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, - drop_ranges: DropRanges, - expr_count: usize, + drop_ranges: DropRangesBuilder, + expr_index: PostOrderId, } impl<'tcx> DropRangeVisitor<'tcx> { fn new(hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, num_exprs: usize) -> Self { debug!("consumed_places: {:?}", places.consumed); - let drop_ranges = DropRanges::new( + let drop_ranges = DropRangesBuilder::new( places.consumed.iter().flat_map(|(_, places)| places.iter().copied()), hir, num_exprs, ); - Self { hir, places, drop_ranges, expr_count: 0 } + Self { hir, places, drop_ranges, expr_index: PostOrderId::from_u32(0) } } fn record_drop(&mut self, hir_id: HirId) { if self.places.borrowed.contains(&hir_id) { debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); } else { - debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - let count = self.expr_count; + debug!("marking {:?} as dropped at {:?}", hir_id, self.expr_index); + let count = self.expr_index; self.drop_ranges.drop_at(hir_id, count); } } @@ -63,7 +67,7 @@ impl<'tcx> DropRangeVisitor<'tcx> { /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all /// expressions. This method consumes a little deeper into the expression when needed. fn consume_expr(&mut self, expr: &hir::Expr<'_>) { - debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); + debug!("consuming expr {:?}, count={:?}", expr.hir_id, self.expr_index); let places = self .places .consumed @@ -80,8 +84,8 @@ impl<'tcx> DropRangeVisitor<'tcx> { hir::Path { res: hir::def::Res::Local(hir_id), .. }, )) = expr.kind { - let location = self.expr_count; - debug!("reinitializing {:?} at {}", hir_id, location); + let location = self.expr_index; + debug!("reinitializing {:?} at {:?}", hir_id, location); self.drop_ranges.reinit_at(*hir_id, location); } else { debug!("reinitializing {:?} is not supported", expr); @@ -102,18 +106,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); - let fork = self.expr_count; + let fork = self.expr_index; - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.drop_ranges.add_control_edge(fork, self.expr_index + 1); self.visit_expr(if_true); - let true_end = self.expr_count; + let true_end = self.expr_index; - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.drop_ranges.add_control_edge(fork, self.expr_index + 1); if let Some(if_false) = if_false { self.visit_expr(if_false); } - self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); + self.drop_ranges.add_control_edge(true_end, self.expr_index + 1); } ExprKind::Assign(lhs, rhs, _) => { self.visit_expr(lhs); @@ -122,18 +126,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } ExprKind::Loop(body, ..) => { - let loop_begin = self.expr_count + 1; + let loop_begin = self.expr_index + 1; self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_count, loop_begin); + self.drop_ranges.add_control_edge(self.expr_index, loop_begin); } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); - let fork = self.expr_count; + let fork = self.expr_index; let arm_end_ids = arms .iter() .map(|hir::Arm { pat, body, guard, .. }| { - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.drop_ranges.add_control_edge(fork, self.expr_index + 1); self.visit_pat(pat); match guard { Some(Guard::If(expr)) => self.visit_expr(expr), @@ -144,23 +148,23 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { None => (), } self.visit_expr(body); - self.expr_count + self.expr_index }) .collect::>(); arm_end_ids.into_iter().for_each(|arm_end| { - self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) + self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1) }); } ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { - self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); + self.drop_ranges.add_control_edge_hir_id(self.expr_index, target); } _ => intravisit::walk_expr(self, expr), } - self.expr_count += 1; - self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); + self.expr_index = self.expr_index + 1; + self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_index); self.consume_expr(expr); if let Some(expr) = reinit { self.reinit_expr(expr); @@ -171,11 +175,11 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { intravisit::walk_pat(self, pat); // Increment expr_count here to match what InteriorVisitor expects. - self.expr_count += 1; + self.expr_index = self.expr_index + 1; } } -impl DropRanges { +impl DropRangesBuilder { fn new(hir_ids: impl Iterator, hir: Map<'_>, num_exprs: usize) -> Self { let mut hir_id_map = HirIdMap::::default(); let mut next = <_>::from(0u32); @@ -204,7 +208,7 @@ impl DropRanges { /// Adds an entry in the mapping from HirIds to PostOrderIds /// /// Needed so that `add_control_edge_hir_id` can work. - fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: usize) { + fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: PostOrderId) { self.post_order_map.insert(hir_id, post_order_id); } @@ -212,16 +216,16 @@ impl DropRanges { /// /// This can be used for branches where we do not know the PostOrderId of the target yet, /// such as when handling `break` or `continue`. - fn add_control_edge_hir_id(&mut self, from: usize, to: HirId) { + fn add_control_edge_hir_id(&mut self, from: PostOrderId, to: HirId) { self.deferred_edges.push((from, to)); } - fn drop_at(&mut self, value: HirId, location: usize) { + fn drop_at(&mut self, value: HirId, location: PostOrderId) { let value = self.hidx(value); self.node_mut(location.into()).drops.push(value); } - fn reinit_at(&mut self, value: HirId, location: usize) { + fn reinit_at(&mut self, value: HirId, location: PostOrderId) { let value = match self.hir_id_map.get(&value) { Some(value) => *value, // If there's no value, this is never consumed and therefore is never dropped. We can @@ -230,4 +234,18 @@ impl DropRanges { }; self.node_mut(location.into()).reinits.push(value); } + + /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them. + /// + /// Should be called after visiting the HIR but before solving the control flow, otherwise some + /// edges will be missed. + fn process_deferred_edges(&mut self) { + let mut edges = vec![]; + swap(&mut edges, &mut self.deferred_edges); + edges.into_iter().for_each(|(from, to)| { + let to = *self.post_order_map.get(&to).expect("Expression ID not found"); + trace!("Adding deferred edge from {:?} to {:?}", from, to); + self.add_control_edge(from, to) + }); + } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs index 74ce762864e0f..22f7484abf3e2 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs @@ -1,12 +1,10 @@ -use super::{DropRanges, PostOrderId}; +use super::{DropRangesBuilder, PostOrderId}; use rustc_index::{bit_set::BitSet, vec::IndexVec}; use std::collections::BTreeMap; -use std::mem::swap; -impl DropRanges { +impl DropRangesBuilder { pub fn propagate_to_fixpoint(&mut self) { trace!("before fixpoint: {:#?}", self); - self.process_deferred_edges(); let preds = self.compute_predecessors(); trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); @@ -53,6 +51,11 @@ impl DropRanges { fn compute_predecessors(&self) -> IndexVec> { let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len()); for (id, node) in self.nodes.iter_enumerated() { + // If the node has no explicit successors, we assume that control + // will from this node into the next one. + // + // If there are successors listed, then we assume that all + // possible successors are given and we do not include the default. if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 { preds[id + 1].push(id); } else { @@ -63,18 +66,4 @@ impl DropRanges { } preds } - - /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them. - /// - /// Should be called after visiting the HIR but before solving the control flow, otherwise some - /// edges will be missed. - fn process_deferred_edges(&mut self) { - let mut edges = vec![]; - swap(&mut edges, &mut self.deferred_edges); - edges.into_iter().for_each(|(from, to)| { - let to = *self.post_order_map.get(&to).expect("Expression ID not found"); - trace!("Adding deferred edge from {} to {}", from, to); - self.add_control_edge(from, to) - }); - } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs index ebbbec1c472b6..b87b3dd9a5f96 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs @@ -3,9 +3,9 @@ use rustc_graphviz as dot; -use super::{DropRanges, PostOrderId}; +use super::{DropRangesBuilder, PostOrderId}; -impl<'a> dot::GraphWalk<'a> for DropRanges { +impl<'a> dot::GraphWalk<'a> for DropRangesBuilder { type Node = PostOrderId; type Edge = (PostOrderId, PostOrderId); @@ -36,7 +36,7 @@ impl<'a> dot::GraphWalk<'a> for DropRanges { } } -impl<'a> dot::Labeller<'a> for DropRanges { +impl<'a> dot::Labeller<'a> for DropRangesBuilder { type Node = PostOrderId; type Edge = (PostOrderId, PostOrderId); @@ -56,7 +56,7 @@ impl<'a> dot::Labeller<'a> for DropRanges { n, self.post_order_map .iter() - .find(|(_hir_id, &post_order_id)| post_order_id == n.index()) + .find(|(_hir_id, &post_order_id)| post_order_id == *n) .map_or("".into(), |(hir_id, _)| format!( "{}", hir_id.local_id.index() From 4a70de79321552fa8c254b9998562d7814f3f72e Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 16 Dec 2021 12:07:36 -0800 Subject: [PATCH 29/69] Handle reinits in match guards --- .../drop_ranges/cfg_build.rs | 21 ++++++++++------ .../drop_ranges/cfg_visualize.rs | 9 +++++++ .../ui/generator/reinit-in-match-guard.rs | 25 +++++++++++++++++++ 3 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/test/ui/generator/reinit-in-match-guard.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index 32f423f3bfeef..0520931d5f605 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -133,11 +133,10 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); - let fork = self.expr_index; - let arm_end_ids = arms - .iter() - .map(|hir::Arm { pat, body, guard, .. }| { - self.drop_ranges.add_control_edge(fork, self.expr_index + 1); + let (guard_exit, arm_end_ids) = arms.iter().fold( + (self.expr_index, vec![]), + |(incoming_edge, mut arm_end_ids), hir::Arm { pat, body, guard, .. }| { + self.drop_ranges.add_control_edge(incoming_edge, self.expr_index + 1); self.visit_pat(pat); match guard { Some(Guard::If(expr)) => self.visit_expr(expr), @@ -147,10 +146,16 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } None => (), } + let to_next_arm = self.expr_index; + // The default edge does not get added since we also have an explicit edge, + // so we also need to add an edge to the next node as well. + self.drop_ranges.add_control_edge(self.expr_index, self.expr_index + 1); self.visit_expr(body); - self.expr_index - }) - .collect::>(); + arm_end_ids.push(self.expr_index); + (to_next_arm, arm_end_ids) + }, + ); + self.drop_ranges.add_control_edge(guard_exit, self.expr_index + 1); arm_end_ids.into_iter().for_each(|arm_end| { self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1) }); diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs index b87b3dd9a5f96..20aad7aedf775 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs @@ -5,6 +5,15 @@ use rustc_graphviz as dot; use super::{DropRangesBuilder, PostOrderId}; +/// Writes the CFG for DropRangesBuilder to a .dot file for visualization. +/// +/// It is not normally called, but is kept around to easily add debugging +/// code when needed. +#[allow(dead_code)] +pub(super) fn write_graph_to_file(drop_ranges: &DropRangesBuilder, filename: &str) { + dot::render(drop_ranges, &mut std::fs::File::create(filename).unwrap()).unwrap(); +} + impl<'a> dot::GraphWalk<'a> for DropRangesBuilder { type Node = PostOrderId; diff --git a/src/test/ui/generator/reinit-in-match-guard.rs b/src/test/ui/generator/reinit-in-match-guard.rs new file mode 100644 index 0000000000000..260b341a52525 --- /dev/null +++ b/src/test/ui/generator/reinit-in-match-guard.rs @@ -0,0 +1,25 @@ +// build-pass + +#![feature(generators)] + +#![allow(unused_assignments, dead_code)] + +fn main() { + let _ = || { + let mut x = vec![22_usize]; + std::mem::drop(x); + match y() { + true if { + x = vec![]; + false + } => {} + _ => { + yield; + } + } + }; +} + +fn y() -> bool { + true +} From 6e281a7782b775104351ab28abaee795fdaf84c5 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 16 Dec 2021 12:38:08 -0800 Subject: [PATCH 30/69] Explicitly list all ExprKinds in cfg_build Also rearranges the existing arms to be more logical. For example, Break and Continue come closer to Loop now. --- .../drop_ranges/cfg_build.rs | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index 0520931d5f605..4656f56569e46 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -103,6 +103,12 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut reinit = None; match expr.kind { + ExprKind::Assign(lhs, rhs, _) => { + self.visit_expr(lhs); + self.visit_expr(rhs); + + reinit = Some(lhs); + } ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); @@ -119,17 +125,6 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.drop_ranges.add_control_edge(true_end, self.expr_index + 1); } - ExprKind::Assign(lhs, rhs, _) => { - self.visit_expr(lhs); - self.visit_expr(rhs); - - reinit = Some(lhs); - } - ExprKind::Loop(body, ..) => { - let loop_begin = self.expr_index + 1; - self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_index, loop_begin); - } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); @@ -160,12 +155,45 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1) }); } + ExprKind::Loop(body, ..) => { + let loop_begin = self.expr_index + 1; + self.visit_block(body); + self.drop_ranges.add_control_edge(self.expr_index, loop_begin); + } ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { self.drop_ranges.add_control_edge_hir_id(self.expr_index, target); } - _ => intravisit::walk_expr(self, expr), + ExprKind::AddrOf(..) + | ExprKind::Array(..) + | ExprKind::AssignOp(..) + | ExprKind::Binary(..) + | ExprKind::Block(..) + | ExprKind::Box(..) + | ExprKind::Break(..) + | ExprKind::Call(..) + | ExprKind::Cast(..) + | ExprKind::Closure(..) + | ExprKind::ConstBlock(..) + | ExprKind::Continue(..) + | ExprKind::DropTemps(..) + | ExprKind::Err + | ExprKind::Field(..) + | ExprKind::Index(..) + | ExprKind::InlineAsm(..) + | ExprKind::Let(..) + | ExprKind::Lit(..) + | ExprKind::LlvmInlineAsm(..) + | ExprKind::MethodCall(..) + | ExprKind::Path(..) + | ExprKind::Repeat(..) + | ExprKind::Ret(..) + | ExprKind::Struct(..) + | ExprKind::Tup(..) + | ExprKind::Type(..) + | ExprKind::Unary(..) + | ExprKind::Yield(..) => intravisit::walk_expr(self, expr), } self.expr_index = self.expr_index + 1; From a7df4e8d2f89379fb0b620cb0267f97c05bc1598 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 16 Dec 2021 13:34:39 -0800 Subject: [PATCH 31/69] Handle empty loops better --- .../check/generator_interior/drop_ranges/cfg_build.rs | 11 +++++++++-- src/test/ui/async-await/async-fn-nonsend.rs | 8 ++++++++ src/test/ui/async-await/async-fn-nonsend.stderr | 8 ++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index 4656f56569e46..b434e05db80b4 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -157,8 +157,15 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } ExprKind::Loop(body, ..) => { let loop_begin = self.expr_index + 1; - self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_index, loop_begin); + if body.stmts.is_empty() && body.expr.is_none() { + // For empty loops we won't have updated self.expr_index after visiting the + // body, meaning we'd get an edge from expr_index to expr_index + 1, but + // instead we want an edge from expr_index + 1 to expr_index + 1. + self.drop_ranges.add_control_edge(loop_begin, loop_begin); + } else { + self.visit_block(body); + self.drop_ranges.add_control_edge(self.expr_index, loop_begin); + } } ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index 210d9ff3f2d32..55629132e400e 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -43,6 +43,13 @@ async fn non_sync_with_method_call() { } } +async fn non_sync_with_infinite_loop() { + let f: &mut std::fmt::Formatter = loop {}; + if non_sync().fmt(f).unwrap() == () { + fut().await; + } +} + fn assert_send(_: impl Send) {} pub fn pass_assert() { @@ -51,4 +58,5 @@ pub fn pass_assert() { //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_method_call()); //~^ ERROR future cannot be sent between threads safely + assert_send(non_sync_with_infinite_loop()); } diff --git a/src/test/ui/async-await/async-fn-nonsend.stderr b/src/test/ui/async-await/async-fn-nonsend.stderr index abba5585c62eb..9c87067a4d3a0 100644 --- a/src/test/ui/async-await/async-fn-nonsend.stderr +++ b/src/test/ui/async-await/async-fn-nonsend.stderr @@ -1,5 +1,5 @@ error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:50:17 + --> $DIR/async-fn-nonsend.rs:57:17 | LL | assert_send(non_send_temporary_in_match()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_send_temporary_in_match` is not `Send` @@ -16,13 +16,13 @@ LL | Some(_) => fut().await, LL | } | - `Some(non_send())` is later dropped here note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:46:24 + --> $DIR/async-fn-nonsend.rs:53:24 | LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:52:17 + --> $DIR/async-fn-nonsend.rs:59:17 | LL | assert_send(non_sync_with_method_call()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_sync_with_method_call` is not `Send` @@ -40,7 +40,7 @@ LL | } LL | } | - `f` is later dropped here note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:46:24 + --> $DIR/async-fn-nonsend.rs:53:24 | LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` From 7d11b336f3ee9d65ba82b86d73cf9bc9fb174269 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 16 Dec 2021 15:46:56 -0800 Subject: [PATCH 32/69] Remove clones and most allocations from propagate_to_fixpoint --- .../generator_interior/drop_ranges/cfg_propagate.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs index 22f7484abf3e2..a540812f4a329 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs @@ -9,16 +9,17 @@ impl DropRangesBuilder { trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); + let mut new_state = BitSet::new_empty(self.num_values()); + let mut propagate = || { let mut changed = false; for id in self.nodes.indices() { - let old_state = self.nodes[id].drop_state.clone(); - let mut new_state = if id.index() == 0 { - BitSet::new_empty(self.num_values()) + if id.index() == 0 { + new_state.clear(); } else { // If we are not the start node and we have no predecessors, treat // everything as dropped because there's no way to get here anyway. - BitSet::new_filled(self.num_values()) + new_state.insert_all(); }; for pred in &preds[id] { @@ -34,8 +35,7 @@ impl DropRangesBuilder { new_state.remove(*reinit); } - changed |= old_state != new_state; - self.nodes[id].drop_state = new_state; + changed |= self.nodes[id].drop_state.intersect(&new_state); } changed From f730bd0dad555e2a5db4b0110b0888ca515f6fef Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 16 Dec 2021 17:15:35 -0800 Subject: [PATCH 33/69] Track changed bitsets in CFG propagation This reduces the amount of work done, especially in later iterations, by only processing nodes whose predecessors changed in the previous iteration, or earlier in the current iteration. This also has the side effect of completely ignoring all unreachable nodes. --- .../drop_ranges/cfg_propagate.rs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs index a540812f4a329..139d17d2e1ca1 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs @@ -10,10 +10,28 @@ impl DropRangesBuilder { trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); let mut new_state = BitSet::new_empty(self.num_values()); + let mut changed_nodes = BitSet::new_empty(self.nodes.len()); + let mut unchanged_mask = BitSet::new_filled(self.nodes.len()); + changed_nodes.insert(0u32.into()); let mut propagate = || { let mut changed = false; + unchanged_mask.insert_all(); for id in self.nodes.indices() { + trace!("processing {:?}, changed_nodes: {:?}", id, changed_nodes); + // Check if any predecessor has changed, and if not then short-circuit. + // + // We handle the start node specially, since it doesn't have any predecessors, + // but we need to start somewhere. + if match id.index() { + 0 => !changed_nodes.contains(id), + _ => !preds[id].iter().any(|pred| changed_nodes.contains(*pred)), + } { + trace!("short-circuiting because none of {:?} have changed", preds[id]); + unchanged_mask.remove(id); + continue; + } + if id.index() == 0 { new_state.clear(); } else { @@ -23,8 +41,7 @@ impl DropRangesBuilder { }; for pred in &preds[id] { - let state = &self.nodes[*pred].drop_state; - new_state.intersect(state); + new_state.intersect(&self.nodes[*pred].drop_state); } for drop in &self.nodes[id].drops { @@ -35,9 +52,15 @@ impl DropRangesBuilder { new_state.remove(*reinit); } - changed |= self.nodes[id].drop_state.intersect(&new_state); + if self.nodes[id].drop_state.intersect(&new_state) { + changed_nodes.insert(id); + changed = true; + } else { + unchanged_mask.remove(id); + } } + changed_nodes.intersect(&unchanged_mask); changed }; From 787f4cbd15b91e88d757bf3f1ac1dadfa0e8ec5a Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 17 Dec 2021 14:36:51 -0800 Subject: [PATCH 34/69] Handle uninhabited return types This changes drop range analysis to handle uninhabited return types such as `!`. Since these calls to these functions do not return, we model them as ending in an infinite loop. --- .../check/generator_interior/drop_ranges.rs | 10 ++- .../drop_ranges/cfg_build.rs | 62 ++++++++++++++++--- src/test/ui/async-await/async-fn-nonsend.rs | 2 - .../ui/async-await/async-fn-nonsend.stderr | 30 +-------- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 9fbefcfb088c3..cf463d0aeaebd 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -36,8 +36,14 @@ pub fn compute_drop_ranges<'a, 'tcx>( let consumed_borrowed_places = find_consumed_and_borrowed(fcx, def_id, body); let num_exprs = fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0); - let mut drop_ranges = - build_control_flow_graph(fcx.tcx.hir(), consumed_borrowed_places, body, num_exprs); + let mut drop_ranges = build_control_flow_graph( + fcx.tcx.hir(), + fcx.tcx, + &fcx.typeck_results.borrow(), + consumed_borrowed_places, + body, + num_exprs, + ); drop_ranges.propagate_to_fixpoint(); diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index b434e05db80b4..1591b144dc62b 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -8,7 +8,10 @@ use hir::{ }; use rustc_hir as hir; use rustc_index::vec::IndexVec; -use rustc_middle::hir::map::Map; +use rustc_middle::{ + hir::map::Map, + ty::{TyCtxt, TypeckResults}, +}; use std::mem::swap; /// Traverses the body to find the control flow graph and locations for the @@ -18,11 +21,14 @@ use std::mem::swap; /// can be done with propagate_to_fixpoint in cfg_propagate. pub(super) fn build_control_flow_graph<'tcx>( hir: Map<'tcx>, + tcx: TyCtxt<'tcx>, + typeck_results: &TypeckResults<'tcx>, consumed_borrowed_places: ConsumedAndBorrowedPlaces, body: &'tcx Body<'tcx>, num_exprs: usize, ) -> DropRangesBuilder { - let mut drop_range_visitor = DropRangeVisitor::new(hir, consumed_borrowed_places, num_exprs); + let mut drop_range_visitor = + DropRangeVisitor::new(hir, tcx, typeck_results, consumed_borrowed_places, num_exprs); intravisit::walk_body(&mut drop_range_visitor, body); drop_range_visitor.drop_ranges.process_deferred_edges(); @@ -36,22 +42,30 @@ pub(super) fn build_control_flow_graph<'tcx>( /// We are interested in points where a variables is dropped or initialized, and the control flow /// of the code. We identify locations in code by their post-order traversal index, so it is /// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. -struct DropRangeVisitor<'tcx> { +struct DropRangeVisitor<'a, 'tcx> { hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, drop_ranges: DropRangesBuilder, expr_index: PostOrderId, + tcx: TyCtxt<'tcx>, + typeck_results: &'a TypeckResults<'tcx>, } -impl<'tcx> DropRangeVisitor<'tcx> { - fn new(hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, num_exprs: usize) -> Self { +impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { + fn new( + hir: Map<'tcx>, + tcx: TyCtxt<'tcx>, + typeck_results: &'a TypeckResults<'tcx>, + places: ConsumedAndBorrowedPlaces, + num_exprs: usize, + ) -> Self { debug!("consumed_places: {:?}", places.consumed); let drop_ranges = DropRangesBuilder::new( places.consumed.iter().flat_map(|(_, places)| places.iter().copied()), hir, num_exprs, ); - Self { hir, places, drop_ranges, expr_index: PostOrderId::from_u32(0) } + Self { hir, places, drop_ranges, expr_index: PostOrderId::from_u32(0), typeck_results, tcx } } fn record_drop(&mut self, hir_id: HirId) { @@ -91,9 +105,23 @@ impl<'tcx> DropRangeVisitor<'tcx> { debug!("reinitializing {:?} is not supported", expr); } } + + /// For an expression with an uninhabited return type (e.g. a function that returns !), + /// this adds a self edge to to the CFG to model the fact that the function does not + /// return. + fn handle_uninhabited_return(&mut self, expr: &Expr<'tcx>) { + let ty = self.typeck_results.expr_ty(expr); + let ty = self.tcx.erase_regions(ty); + let m = self.tcx.parent_module(expr.hir_id).to_def_id(); + let param_env = self.tcx.param_env(m.expect_local()); + if self.tcx.is_ty_uninhabited_from(m, ty, param_env) { + // This function will not return. We model this fact as an infinite loop. + self.drop_ranges.add_control_edge(self.expr_index + 1, self.expr_index + 1); + } + } } -impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { +impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { type Map = intravisit::ErasedMap<'tcx>; fn nested_visit_map(&mut self) -> NestedVisitorMap { @@ -109,6 +137,7 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } + ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); @@ -155,6 +184,7 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1) }); } + ExprKind::Loop(body, ..) => { let loop_begin = self.expr_index + 1; if body.stmts.is_empty() && body.expr.is_none() { @@ -172,6 +202,22 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.drop_ranges.add_control_edge_hir_id(self.expr_index, target); } + ExprKind::Call(f, args) => { + self.visit_expr(f); + for arg in args { + self.visit_expr(arg); + } + + self.handle_uninhabited_return(expr); + } + ExprKind::MethodCall(_, _, exprs, _) => { + for expr in exprs { + self.visit_expr(expr); + } + + self.handle_uninhabited_return(expr); + } + ExprKind::AddrOf(..) | ExprKind::Array(..) | ExprKind::AssignOp(..) @@ -179,7 +225,6 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { | ExprKind::Block(..) | ExprKind::Box(..) | ExprKind::Break(..) - | ExprKind::Call(..) | ExprKind::Cast(..) | ExprKind::Closure(..) | ExprKind::ConstBlock(..) @@ -192,7 +237,6 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { | ExprKind::Let(..) | ExprKind::Lit(..) | ExprKind::LlvmInlineAsm(..) - | ExprKind::MethodCall(..) | ExprKind::Path(..) | ExprKind::Repeat(..) | ExprKind::Ret(..) diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index 55629132e400e..123cadc2cbb04 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -36,7 +36,6 @@ async fn non_send_temporary_in_match() { } async fn non_sync_with_method_call() { - // FIXME: it would be nice for this to work let f: &mut std::fmt::Formatter = panic!(); if non_sync().fmt(f).unwrap() == () { fut().await; @@ -57,6 +56,5 @@ pub fn pass_assert() { assert_send(non_send_temporary_in_match()); //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_method_call()); - //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_infinite_loop()); } diff --git a/src/test/ui/async-await/async-fn-nonsend.stderr b/src/test/ui/async-await/async-fn-nonsend.stderr index 9c87067a4d3a0..be42d46a9060e 100644 --- a/src/test/ui/async-await/async-fn-nonsend.stderr +++ b/src/test/ui/async-await/async-fn-nonsend.stderr @@ -1,5 +1,5 @@ error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:57:17 + --> $DIR/async-fn-nonsend.rs:56:17 | LL | assert_send(non_send_temporary_in_match()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_send_temporary_in_match` is not `Send` @@ -16,34 +16,10 @@ LL | Some(_) => fut().await, LL | } | - `Some(non_send())` is later dropped here note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:53:24 + --> $DIR/async-fn-nonsend.rs:52:24 | LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` -error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:59:17 - | -LL | assert_send(non_sync_with_method_call()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_sync_with_method_call` is not `Send` - | - = help: the trait `Send` is not implemented for `dyn std::fmt::Write` -note: future is not `Send` as this value is used across an await - --> $DIR/async-fn-nonsend.rs:42:14 - | -LL | let f: &mut std::fmt::Formatter = panic!(); - | - has type `&mut Formatter<'_>` which is not `Send` -LL | if non_sync().fmt(f).unwrap() == () { -LL | fut().await; - | ^^^^^^ await occurs here, with `f` maybe used later -LL | } -LL | } - | - `f` is later dropped here -note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:53:24 - | -LL | fn assert_send(_: impl Send) {} - | ^^^^ required by this bound in `assert_send` - -error: aborting due to 2 previous errors +error: aborting due to previous error From 887e843eeb35e9cc78884e9d5feacf914377f355 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 17 Dec 2021 15:05:38 -0800 Subject: [PATCH 35/69] Update async-fn-nonsend.rs The previous commit made the non_sync_with_method_call case pass due to the await being unreachable. Unfortunately, this isn't actually the behavior the test was verifying. This change lifts the panic into a helper function so that the generator analysis still thinks the await is reachable, and therefore we preserve the same testing behavior. --- src/test/ui/async-await/async-fn-nonsend.rs | 18 +++++++++-- .../ui/async-await/async-fn-nonsend.stderr | 30 +++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index 123cadc2cbb04..c5453b67ef5b6 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -35,14 +35,26 @@ async fn non_send_temporary_in_match() { } } +fn get_formatter() -> std::fmt::Formatter<'static> { + panic!() +} + async fn non_sync_with_method_call() { + let f: &mut std::fmt::Formatter = &mut get_formatter(); + // It would by nice for this to work. + if non_sync().fmt(f).unwrap() == () { + fut().await; + } +} + +async fn non_sync_with_method_call_panic() { let f: &mut std::fmt::Formatter = panic!(); if non_sync().fmt(f).unwrap() == () { fut().await; } } -async fn non_sync_with_infinite_loop() { +async fn non_sync_with_method_call_infinite_loop() { let f: &mut std::fmt::Formatter = loop {}; if non_sync().fmt(f).unwrap() == () { fut().await; @@ -56,5 +68,7 @@ pub fn pass_assert() { assert_send(non_send_temporary_in_match()); //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_method_call()); - assert_send(non_sync_with_infinite_loop()); + //~^ ERROR future cannot be sent between threads safely + assert_send(non_sync_with_method_call_panic()); + assert_send(non_sync_with_method_call_infinite_loop()); } diff --git a/src/test/ui/async-await/async-fn-nonsend.stderr b/src/test/ui/async-await/async-fn-nonsend.stderr index be42d46a9060e..40ad46b48620d 100644 --- a/src/test/ui/async-await/async-fn-nonsend.stderr +++ b/src/test/ui/async-await/async-fn-nonsend.stderr @@ -1,5 +1,5 @@ error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:56:17 + --> $DIR/async-fn-nonsend.rs:68:17 | LL | assert_send(non_send_temporary_in_match()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_send_temporary_in_match` is not `Send` @@ -16,10 +16,34 @@ LL | Some(_) => fut().await, LL | } | - `Some(non_send())` is later dropped here note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:52:24 + --> $DIR/async-fn-nonsend.rs:64:24 | LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` -error: aborting due to previous error +error: future cannot be sent between threads safely + --> $DIR/async-fn-nonsend.rs:70:17 + | +LL | assert_send(non_sync_with_method_call()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_sync_with_method_call` is not `Send` + | + = help: the trait `Send` is not implemented for `dyn std::fmt::Write` +note: future is not `Send` as this value is used across an await + --> $DIR/async-fn-nonsend.rs:46:14 + | +LL | let f: &mut std::fmt::Formatter = &mut get_formatter(); + | --------------- has type `Formatter<'_>` which is not `Send` +... +LL | fut().await; + | ^^^^^^ await occurs here, with `get_formatter()` maybe used later +LL | } +LL | } + | - `get_formatter()` is later dropped here +note: required by a bound in `assert_send` + --> $DIR/async-fn-nonsend.rs:64:24 + | +LL | fn assert_send(_: impl Send) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to 2 previous errors From 78c5644de5ffea9d64200bd28eac7e49ca2c8f33 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 20 Dec 2021 15:50:31 -0800 Subject: [PATCH 36/69] drop_ranges: Add TrackedValue enum This makes it clearer what values we are tracking and why. --- .../check/generator_interior/drop_ranges.rs | 75 +++++++++++++++---- .../drop_ranges/cfg_build.rs | 63 +++++++++------- .../drop_ranges/record_consumed_borrow.rs | 35 +++------ 3 files changed, 106 insertions(+), 67 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index cf463d0aeaebd..681cd7cf935f5 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -17,9 +17,12 @@ use self::record_consumed_borrow::find_consumed_and_borrowed; use crate::check::FnCtxt; use hir::def_id::DefId; use hir::{Body, HirId, HirIdMap, Node}; +use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; +use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId}; +use rustc_middle::ty; use std::collections::BTreeMap; use std::fmt::Debug; @@ -47,13 +50,17 @@ pub fn compute_drop_ranges<'a, 'tcx>( drop_ranges.propagate_to_fixpoint(); - DropRanges { hir_id_map: drop_ranges.hir_id_map, nodes: drop_ranges.nodes } + DropRanges { tracked_value_map: drop_ranges.tracked_value_map, nodes: drop_ranges.nodes } } /// Applies `f` to consumable portion of a HIR node. /// /// The `node` parameter should be the result of calling `Map::find(place)`. -fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(HirId)) { +fn for_each_consumable( + place: TrackedValue, + node: Option>, + mut f: impl FnMut(TrackedValue), +) { f(place); if let Some(Node::Expr(expr)) = node { match expr.kind { @@ -61,7 +68,7 @@ fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(H _, hir::Path { res: hir::def::Res::Local(hir_id), .. }, )) => { - f(*hir_id); + f(TrackedValue::Variable(*hir_id)); } _ => (), } @@ -75,22 +82,60 @@ rustc_index::newtype_index! { } rustc_index::newtype_index! { - pub struct HirIdIndex { + pub struct TrackedValueIndex { DEBUG_FORMAT = "hidx({})", } } +/// Identifies a value whose drop state we need to track. +#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] +enum TrackedValue { + /// Represents a named variable, such as a let binding, parameter, or upvar. + /// + /// The HirId points to the variable's definition site. + Variable(HirId), + /// A value produced as a result of an expression. + /// + /// The HirId points to the expression that returns this value. + Temporary(HirId), +} + +impl TrackedValue { + fn hir_id(&self) -> HirId { + match self { + TrackedValue::Variable(hir_id) | TrackedValue::Temporary(hir_id) => *hir_id, + } + } +} + +impl From<&PlaceWithHirId<'_>> for TrackedValue { + fn from(place_with_id: &PlaceWithHirId<'_>) -> Self { + match place_with_id.place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => { + TrackedValue::Temporary(place_with_id.hir_id) + } + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => { + TrackedValue::Variable(hir_id) + } + } + } +} + pub struct DropRanges { - hir_id_map: HirIdMap, + tracked_value_map: FxHashMap, nodes: IndexVec, } impl DropRanges { pub fn is_dropped_at(&self, hir_id: HirId, location: usize) -> bool { - self.hir_id_map - .get(&hir_id) - .copied() - .map_or(false, |hir_id| self.expect_node(location.into()).drop_state.contains(hir_id)) + self.tracked_value_map + .get(&TrackedValue::Temporary(hir_id)) + .or(self.tracked_value_map.get(&TrackedValue::Variable(hir_id))) + .cloned() + .map_or(false, |tracked_value_id| { + self.expect_node(location.into()).drop_state.contains(tracked_value_id) + }) } /// Returns a reference to the NodeInfo for a node, panicking if it does not exist @@ -118,7 +163,7 @@ struct DropRangesBuilder { /// (see NodeInfo::drop_state). The hir_id_map field stores the mapping /// from HirIds to the HirIdIndex that is used to represent that value in /// bitvector. - hir_id_map: HirIdMap, + tracked_value_map: FxHashMap, /// When building the control flow graph, we don't always know the /// post-order index of the target node at the point we encounter it. @@ -138,7 +183,7 @@ struct DropRangesBuilder { impl Debug for DropRangesBuilder { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DropRanges") - .field("hir_id_map", &self.hir_id_map) + .field("hir_id_map", &self.tracked_value_map) .field("post_order_maps", &self.post_order_map) .field("nodes", &self.nodes.iter_enumerated().collect::>()) .finish() @@ -154,7 +199,7 @@ impl Debug for DropRangesBuilder { impl DropRangesBuilder { /// Returns the number of values (hir_ids) that are tracked fn num_values(&self) -> usize { - self.hir_id_map.len() + self.tracked_value_map.len() } fn node_mut(&mut self, id: PostOrderId) -> &mut NodeInfo { @@ -177,13 +222,13 @@ struct NodeInfo { successors: Vec, /// List of hir_ids that are dropped by this node. - drops: Vec, + drops: Vec, /// List of hir_ids that are reinitialized by this node. - reinits: Vec, + reinits: Vec, /// Set of values that are definitely dropped at this point. - drop_state: BitSet, + drop_state: BitSet, } impl NodeInfo { diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index 1591b144dc62b..dfe8ed54b2192 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -1,11 +1,12 @@ use super::{ for_each_consumable, record_consumed_borrow::ConsumedAndBorrowedPlaces, DropRangesBuilder, - HirIdIndex, NodeInfo, PostOrderId, + NodeInfo, PostOrderId, TrackedValue, TrackedValueIndex, }; use hir::{ intravisit::{self, NestedVisitorMap, Visitor}, - Body, Expr, ExprKind, Guard, HirId, HirIdMap, + Body, Expr, ExprKind, Guard, HirId, }; +use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_index::vec::IndexVec; use rustc_middle::{ @@ -61,20 +62,20 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { ) -> Self { debug!("consumed_places: {:?}", places.consumed); let drop_ranges = DropRangesBuilder::new( - places.consumed.iter().flat_map(|(_, places)| places.iter().copied()), + places.consumed.iter().flat_map(|(_, places)| places.iter().cloned()), hir, num_exprs, ); Self { hir, places, drop_ranges, expr_index: PostOrderId::from_u32(0), typeck_results, tcx } } - fn record_drop(&mut self, hir_id: HirId) { - if self.places.borrowed.contains(&hir_id) { - debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); + fn record_drop(&mut self, value: TrackedValue) { + if self.places.borrowed.contains(&value) { + debug!("not marking {:?} as dropped because it is borrowed at some point", value); } else { - debug!("marking {:?} as dropped at {:?}", hir_id, self.expr_index); + debug!("marking {:?} as dropped at {:?}", value, self.expr_index); let count = self.expr_index; - self.drop_ranges.drop_at(hir_id, count); + self.drop_ranges.drop_at(value, count); } } @@ -88,7 +89,9 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { .get(&expr.hir_id) .map_or(vec![], |places| places.iter().cloned().collect()); for place in places { - for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); + for_each_consumable(place, self.hir.find(place.hir_id()), |value| { + self.record_drop(value) + }); } } @@ -100,7 +103,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { { let location = self.expr_index; debug!("reinitializing {:?} at {:?}", hir_id, location); - self.drop_ranges.reinit_at(*hir_id, location); + self.drop_ranges.reinit_at(TrackedValue::Variable(*hir_id), location); } else { debug!("reinitializing {:?} is not supported", expr); } @@ -264,36 +267,40 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { } impl DropRangesBuilder { - fn new(hir_ids: impl Iterator, hir: Map<'_>, num_exprs: usize) -> Self { - let mut hir_id_map = HirIdMap::::default(); + fn new( + tracked_values: impl Iterator, + hir: Map<'_>, + num_exprs: usize, + ) -> Self { + let mut tracked_value_map = FxHashMap::<_, TrackedValueIndex>::default(); let mut next = <_>::from(0u32); - for hir_id in hir_ids { - for_each_consumable(hir_id, hir.find(hir_id), |hir_id| { - if !hir_id_map.contains_key(&hir_id) { - hir_id_map.insert(hir_id, next); - next = <_>::from(next.index() + 1); + for value in tracked_values { + for_each_consumable(value, hir.find(value.hir_id()), |value| { + if !tracked_value_map.contains_key(&value) { + tracked_value_map.insert(value, next); + next = next + 1; } }); } - debug!("hir_id_map: {:?}", hir_id_map); - let num_values = hir_id_map.len(); + debug!("hir_id_map: {:?}", tracked_value_map); + let num_values = tracked_value_map.len(); Self { - hir_id_map, + tracked_value_map, nodes: IndexVec::from_fn_n(|_| NodeInfo::new(num_values), num_exprs + 1), deferred_edges: <_>::default(), post_order_map: <_>::default(), } } - fn hidx(&self, hir_id: HirId) -> HirIdIndex { - *self.hir_id_map.get(&hir_id).unwrap() + fn tracked_value_index(&self, tracked_value: TrackedValue) -> TrackedValueIndex { + *self.tracked_value_map.get(&tracked_value).unwrap() } /// Adds an entry in the mapping from HirIds to PostOrderIds /// /// Needed so that `add_control_edge_hir_id` can work. - fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: PostOrderId) { - self.post_order_map.insert(hir_id, post_order_id); + fn add_node_mapping(&mut self, node_hir_id: HirId, post_order_id: PostOrderId) { + self.post_order_map.insert(node_hir_id, post_order_id); } /// Like add_control_edge, but uses a hir_id as the target. @@ -304,13 +311,13 @@ impl DropRangesBuilder { self.deferred_edges.push((from, to)); } - fn drop_at(&mut self, value: HirId, location: PostOrderId) { - let value = self.hidx(value); + fn drop_at(&mut self, value: TrackedValue, location: PostOrderId) { + let value = self.tracked_value_index(value); self.node_mut(location.into()).drops.push(value); } - fn reinit_at(&mut self, value: HirId, location: PostOrderId) { - let value = match self.hir_id_map.get(&value) { + fn reinit_at(&mut self, value: TrackedValue, location: PostOrderId) { + let value = match self.tracked_value_map.get(&value) { Some(value) => *value, // If there's no value, this is never consumed and therefore is never dropped. We can // ignore this. diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs index 36e843e7fd1f3..2548b60809281 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -1,16 +1,14 @@ +use super::TrackedValue; use crate::{ check::FnCtxt, expr_use_visitor::{self, ExprUseVisitor}, }; -use hir::{def_id::DefId, Body, HirId, HirIdMap, HirIdSet}; +use hir::{def_id::DefId, Body, HirId, HirIdMap}; +use rustc_data_structures::stable_set::FxHashSet; use rustc_hir as hir; -use rustc_middle::hir::{ - map::Map, - place::{Place, PlaceBase}, -}; -use rustc_middle::ty; +use rustc_middle::hir::map::Map; -pub fn find_consumed_and_borrowed<'a, 'tcx>( +pub(super) fn find_consumed_and_borrowed<'a, 'tcx>( fcx: &'a FnCtxt<'a, 'tcx>, def_id: DefId, body: &'tcx Body<'tcx>, @@ -20,7 +18,7 @@ pub fn find_consumed_and_borrowed<'a, 'tcx>( expr_use_visitor.places } -pub struct ConsumedAndBorrowedPlaces { +pub(super) struct ConsumedAndBorrowedPlaces { /// Records the variables/expressions that are dropped by a given expression. /// /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables @@ -28,9 +26,9 @@ pub struct ConsumedAndBorrowedPlaces { /// /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is /// not considered a drop of `x`, although it would be a drop of `x.y`. - pub consumed: HirIdMap, + pub(super) consumed: HirIdMap>, /// A set of hir-ids of values or variables that are borrowed at some point within the body. - pub borrowed: HirIdSet, + pub(super) borrowed: FxHashSet, } /// Works with ExprUseVisitor to find interesting values for the drop range analysis. @@ -65,7 +63,7 @@ impl<'tcx> ExprUseDelegate<'tcx> { .consume_body(body); } - fn mark_consumed(&mut self, consumer: HirId, target: HirId) { + fn mark_consumed(&mut self, consumer: HirId, target: TrackedValue) { if !self.places.consumed.contains_key(&consumer) { self.places.consumed.insert(consumer, <_>::default()); } @@ -87,8 +85,7 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { "consume {:?}; diag_expr_id={:?}, using parent {:?}", place_with_id, diag_expr_id, parent ); - self.mark_consumed(parent, place_with_id.hir_id); - place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); + self.mark_consumed(parent, place_with_id.into()); } fn borrow( @@ -97,7 +94,7 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { _diag_expr_id: HirId, _bk: rustc_middle::ty::BorrowKind, ) { - place_hir_id(&place_with_id.place).map(|place| self.places.borrowed.insert(place)); + self.places.borrowed.insert(place_with_id.into()); } fn mutate( @@ -115,13 +112,3 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { ) { } } - -/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to -/// track for a value in the drop range analysis. -fn place_hir_id(place: &Place<'_>) -> Option { - match place.base { - PlaceBase::Rvalue | PlaceBase::StaticItem => None, - PlaceBase::Local(hir_id) - | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), - } -} From 32930d9ea7cc79239daa19a040cbae9867053af8 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 5 Jan 2022 14:11:37 -0800 Subject: [PATCH 37/69] Safely handle partial drops We previously weren't tracking partial re-inits while being too aggressive around partial drops. With this change, we simply ignore partial drops, which is the safer, more conservative choice. --- .../drop_ranges/record_consumed_borrow.rs | 6 +++- .../partial-drop-partial-reinit.rs | 29 +++++++++++++++++++ .../partial-drop-partial-reinit.stderr | 27 +++++++++++++++++ src/test/ui/generator/partial-drop.rs | 4 +-- src/test/ui/generator/partial-drop.stderr | 25 ++++++++++++++++ 5 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/test/ui/async-await/partial-drop-partial-reinit.rs create mode 100644 src/test/ui/async-await/partial-drop-partial-reinit.stderr create mode 100644 src/test/ui/generator/partial-drop.stderr diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs index 2548b60809281..845cd01a44eed 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -85,7 +85,11 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { "consume {:?}; diag_expr_id={:?}, using parent {:?}", place_with_id, diag_expr_id, parent ); - self.mark_consumed(parent, place_with_id.into()); + // We do not currently support partial drops or reinits, so just ignore + // any places with projections. + if place_with_id.place.projections.is_empty() { + self.mark_consumed(parent, place_with_id.into()); + } } fn borrow( diff --git a/src/test/ui/async-await/partial-drop-partial-reinit.rs b/src/test/ui/async-await/partial-drop-partial-reinit.rs new file mode 100644 index 0000000000000..73f0ca8153cb9 --- /dev/null +++ b/src/test/ui/async-await/partial-drop-partial-reinit.rs @@ -0,0 +1,29 @@ +// edition:2021 +#![feature(negative_impls)] +#![allow(unused)] + +fn main() { + gimme_send(foo()); + //~^ ERROR cannot be sent between threads safely +} + +fn gimme_send(t: T) { + drop(t); +} + +struct NotSend {} + +impl Drop for NotSend { + fn drop(&mut self) {} +} + +impl !Send for NotSend {} + +async fn foo() { + let mut x = (NotSend {},); + drop(x.0); + x.0 = NotSend {}; + bar().await; +} + +async fn bar() {} diff --git a/src/test/ui/async-await/partial-drop-partial-reinit.stderr b/src/test/ui/async-await/partial-drop-partial-reinit.stderr new file mode 100644 index 0000000000000..2097642eb24ab --- /dev/null +++ b/src/test/ui/async-await/partial-drop-partial-reinit.stderr @@ -0,0 +1,27 @@ +error[E0277]: `NotSend` cannot be sent between threads safely + --> $DIR/partial-drop-partial-reinit.rs:6:16 + | +LL | gimme_send(foo()); + | ---------- ^^^^^ `NotSend` cannot be sent between threads safely + | | + | required by a bound introduced by this call +... +LL | async fn foo() { + | - within this `impl Future` + | + = help: within `impl Future`, the trait `Send` is not implemented for `NotSend` + = note: required because it appears within the type `(NotSend,)` + = note: required because it appears within the type `{ResumeTy, (NotSend,), impl Future, ()}` + = note: required because it appears within the type `[static generator@$DIR/partial-drop-partial-reinit.rs:22:16: 27:2]` + = note: required because it appears within the type `from_generator::GenFuture<[static generator@$DIR/partial-drop-partial-reinit.rs:22:16: 27:2]>` + = note: required because it appears within the type `impl Future` + = note: required because it appears within the type `impl Future` +note: required by a bound in `gimme_send` + --> $DIR/partial-drop-partial-reinit.rs:10:18 + | +LL | fn gimme_send(t: T) { + | ^^^^ required by this bound in `gimme_send` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/src/test/ui/generator/partial-drop.rs b/src/test/ui/generator/partial-drop.rs index a2f616aa31336..c8c07ba41c7e8 100644 --- a/src/test/ui/generator/partial-drop.rs +++ b/src/test/ui/generator/partial-drop.rs @@ -1,5 +1,3 @@ -// check-pass - #![feature(negative_impls, generators)] struct Foo; @@ -12,6 +10,8 @@ struct Bar { fn main() { assert_send(|| { + //~^ ERROR generator cannot be sent between threads safely + // FIXME: it would be nice to make this work. let guard = Bar { foo: Foo, x: 42 }; drop(guard.foo); yield; diff --git a/src/test/ui/generator/partial-drop.stderr b/src/test/ui/generator/partial-drop.stderr new file mode 100644 index 0000000000000..93112f52208a5 --- /dev/null +++ b/src/test/ui/generator/partial-drop.stderr @@ -0,0 +1,25 @@ +error: generator cannot be sent between threads safely + --> $DIR/partial-drop.rs:12:5 + | +LL | assert_send(|| { + | ^^^^^^^^^^^ generator is not `Send` + | + = help: within `[generator@$DIR/partial-drop.rs:12:17: 18:6]`, the trait `Send` is not implemented for `Foo` +note: generator is not `Send` as this value is used across a yield + --> $DIR/partial-drop.rs:17:9 + | +LL | let guard = Bar { foo: Foo, x: 42 }; + | ----- has type `Bar` which is not `Send` +LL | drop(guard.foo); +LL | yield; + | ^^^^^ yield occurs here, with `guard` maybe used later +LL | }) + | - `guard` is later dropped here +note: required by a bound in `assert_send` + --> $DIR/partial-drop.rs:21:19 + | +LL | fn assert_send(_: T) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to previous error + From e0a5370ef00938db0e76f6d7845befb51be629ff Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 14 Jan 2022 17:45:00 -0800 Subject: [PATCH 38/69] Respond to code review comments --- .../check/generator_interior/drop_ranges.rs | 48 +++-- .../drop_ranges/cfg_build.rs | 167 ++++++++++++++++-- .../drop_ranges/record_consumed_borrow.rs | 12 +- src/test/ui/generator/partial-drop.rs | 21 ++- src/test/ui/generator/partial-drop.stderr | 52 +++++- 5 files changed, 265 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 681cd7cf935f5..21a8d7b563456 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -21,6 +21,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; +use rustc_middle::hir::map::Map; use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId}; use rustc_middle::ty; use std::collections::BTreeMap; @@ -53,15 +54,18 @@ pub fn compute_drop_ranges<'a, 'tcx>( DropRanges { tracked_value_map: drop_ranges.tracked_value_map, nodes: drop_ranges.nodes } } -/// Applies `f` to consumable portion of a HIR node. +/// Applies `f` to consumable node in the HIR subtree pointed to by `place`. /// -/// The `node` parameter should be the result of calling `Map::find(place)`. -fn for_each_consumable( - place: TrackedValue, - node: Option>, - mut f: impl FnMut(TrackedValue), -) { +/// This includes the place itself, and if the place is a reference to a local +/// variable then `f` is also called on the HIR node for that variable as well. +/// +/// For example, if `place` points to `foo()`, then `f` is called once for the +/// result of `foo`. On the other hand, if `place` points to `x` then `f` will +/// be called both on the `ExprKind::Path` node that represents the expression +/// as well as the HirId of the local `x` itself. +fn for_each_consumable<'tcx>(hir: Map<'tcx>, place: TrackedValue, mut f: impl FnMut(TrackedValue)) { f(place); + let node = hir.find(place.hir_id()); if let Some(Node::Expr(expr)) = node { match expr.kind { hir::ExprKind::Path(hir::QPath::Resolved( @@ -108,15 +112,37 @@ impl TrackedValue { } } -impl From<&PlaceWithHirId<'_>> for TrackedValue { - fn from(place_with_id: &PlaceWithHirId<'_>) -> Self { +/// Represents a reason why we might not be able to convert a HirId or Place +/// into a tracked value. +#[derive(Debug)] +enum TrackedValueConversionError { + /// Place projects are not currently supported. + /// + /// The reasoning around these is kind of subtle, so we choose to be more + /// conservative around these for now. There is not reason in theory we + /// cannot support these, we just have not implemented it yet. + PlaceProjectionsNotSupported, +} + +impl TryFrom<&PlaceWithHirId<'_>> for TrackedValue { + type Error = TrackedValueConversionError; + + fn try_from(place_with_id: &PlaceWithHirId<'_>) -> Result { + if !place_with_id.place.projections.is_empty() { + debug!( + "TrackedValue from PlaceWithHirId: {:?} has projections, which are not supported.", + place_with_id + ); + return Err(TrackedValueConversionError::PlaceProjectionsNotSupported); + } + match place_with_id.place.base { PlaceBase::Rvalue | PlaceBase::StaticItem => { - TrackedValue::Temporary(place_with_id.hir_id) + Ok(TrackedValue::Temporary(place_with_id.hir_id)) } PlaceBase::Local(hir_id) | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => { - TrackedValue::Variable(hir_id) + Ok(TrackedValue::Variable(hir_id)) } } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index dfe8ed54b2192..d7305957f94f8 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -43,6 +43,41 @@ pub(super) fn build_control_flow_graph<'tcx>( /// We are interested in points where a variables is dropped or initialized, and the control flow /// of the code. We identify locations in code by their post-order traversal index, so it is /// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. +/// +/// We make several simplifying assumptions, with the goal of being more conservative than +/// necessary rather than less conservative (since being less conservative is unsound, but more +/// conservative is still safe). These assumptions are: +/// +/// 1. Moving a variable `a` counts as a move of the whole variable. +/// 2. Moving a partial path like `a.b.c` is ignored. +/// 3. Reinitializing through a field (e.g. `a.b.c = 5`) counds as a reinitialization of all of +/// `a`. +/// +/// Some examples: +/// +/// Rule 1: +/// ```rust +/// let mut a = (vec![0], vec![0]); +/// drop(a); +/// // `a` is not considered initialized. +/// ``` +/// +/// Rule 2: +/// ```rust +/// let mut a = (vec![0], vec![0]); +/// drop(a.0); +/// drop(a.1); +/// // `a` is still considered initialized. +/// ``` +/// +/// Rule 3: +/// ```rust +/// let mut a = (vec![0], vec![0]); +/// drop(a); +/// a.1 = vec![1]; +/// // all of `a` is considered initialized +/// ``` + struct DropRangeVisitor<'a, 'tcx> { hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, @@ -89,23 +124,76 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { .get(&expr.hir_id) .map_or(vec![], |places| places.iter().cloned().collect()); for place in places { - for_each_consumable(place, self.hir.find(place.hir_id()), |value| { - self.record_drop(value) - }); + for_each_consumable(self.hir, place, |value| self.record_drop(value)); } } + /// Marks an expression as being reinitialized. + /// + /// Note that we always approximated on the side of things being more + /// initialized than they actually are, as opposed to less. In cases such + /// as `x.y = ...`, we would consider all of `x` as being initialized + /// instead of just the `y` field. + /// + /// This is because it is always safe to consider something initialized + /// even when it is not, but the other way around will cause problems. + /// + /// In the future, we will hopefully tighten up these rules to be more + /// precise. fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { - if let ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) = expr.kind - { - let location = self.expr_index; - debug!("reinitializing {:?} at {:?}", hir_id, location); - self.drop_ranges.reinit_at(TrackedValue::Variable(*hir_id), location); - } else { - debug!("reinitializing {:?} is not supported", expr); + // Walk the expression to find the base. For example, in an expression + // like `*a[i].x`, we want to find the `a` and mark that as + // reinitialized. + match expr.kind { + ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + // This is the base case, where we have found an actual named variable. + + let location = self.expr_index; + debug!("reinitializing {:?} at {:?}", hir_id, location); + self.drop_ranges.reinit_at(TrackedValue::Variable(*hir_id), location); + } + + ExprKind::Field(base, _) => self.reinit_expr(base), + + // Most expressions do not refer to something where we need to track + // reinitializations. + // + // Some of these may be interesting in the future + ExprKind::Path(..) + | ExprKind::Box(_) + | ExprKind::ConstBlock(_) + | ExprKind::Array(_) + | ExprKind::Call(_, _) + | ExprKind::MethodCall(_, _, _, _) + | ExprKind::Tup(_) + | ExprKind::Binary(_, _, _) + | ExprKind::Unary(_, _) + | ExprKind::Lit(_) + | ExprKind::Cast(_, _) + | ExprKind::Type(_, _) + | ExprKind::DropTemps(_) + | ExprKind::Let(_, _, _) + | ExprKind::If(_, _, _) + | ExprKind::Loop(_, _, _, _) + | ExprKind::Match(_, _, _) + | ExprKind::Closure(_, _, _, _, _) + | ExprKind::Block(_, _) + | ExprKind::Assign(_, _, _) + | ExprKind::AssignOp(_, _, _) + | ExprKind::Index(_, _) + | ExprKind::AddrOf(_, _, _) + | ExprKind::Break(_, _) + | ExprKind::Continue(_) + | ExprKind::Ret(_) + | ExprKind::InlineAsm(_) + | ExprKind::LlvmInlineAsm(_) + | ExprKind::Struct(_, _, _) + | ExprKind::Repeat(_, _) + | ExprKind::Yield(_, _) + | ExprKind::Err => (), } } @@ -158,13 +246,47 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { self.drop_ranges.add_control_edge(true_end, self.expr_index + 1); } ExprKind::Match(scrutinee, arms, ..) => { + // We walk through the match expression almost like a chain of if expressions. + // Here's a diagram to follow along with: + // + // ┌─┐ + // match │A│ { + // ┌───┴─┘ + // │ + // ┌▼┌───►┌─┐ ┌─┐ + // │B│ if │C│ =>│D│, + // └─┘ ├─┴──►└─┴──────┐ + // ┌──┘ │ + // ┌──┘ │ + // │ │ + // ┌▼┌───►┌─┐ ┌─┐ │ + // │E│ if │F│ =>│G│, │ + // └─┘ ├─┴──►└─┴┐ │ + // │ │ │ + // } ▼ ▼ │ + // ┌─┐◄───────────────────┘ + // │H│ + // └─┘ + // + // The order we want is that the scrutinee (A) flows into the first pattern (B), + // which flows into the guard (C). Then the guard either flows into the arm body + // (D) or into the start of the next arm (E). Finally, the body flows to the end + // of the match block (H). + // + // The subsequent arms follow the same ordering. First we go to the pattern, then + // the guard (if present, otherwise it flows straight into the body), then into + // the body and then to the end of the match expression. + // + // The comments below show which edge is being added. self.visit_expr(scrutinee); let (guard_exit, arm_end_ids) = arms.iter().fold( (self.expr_index, vec![]), |(incoming_edge, mut arm_end_ids), hir::Arm { pat, body, guard, .. }| { + // A -> B, or C -> E self.drop_ranges.add_control_edge(incoming_edge, self.expr_index + 1); self.visit_pat(pat); + // B -> C and E -> F are added implicitly due to the traversal order. match guard { Some(Guard::If(expr)) => self.visit_expr(expr), Some(Guard::IfLet(pat, expr)) => { @@ -173,17 +295,34 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { } None => (), } + // Likewise, C -> D and F -> G are added implicitly. + + // Save C, F, so we can add the other outgoing edge. let to_next_arm = self.expr_index; + // The default edge does not get added since we also have an explicit edge, // so we also need to add an edge to the next node as well. + // + // This adds C -> D, F -> G self.drop_ranges.add_control_edge(self.expr_index, self.expr_index + 1); self.visit_expr(body); + + // Save the end of the body so we can add the exit edge once we know where + // the exit is. arm_end_ids.push(self.expr_index); + + // Pass C to the next iteration, as well as vec![D] + // + // On the last round through, we pass F and vec![D, G] so that we can + // add all the exit edges. (to_next_arm, arm_end_ids) }, ); + // F -> H self.drop_ranges.add_control_edge(guard_exit, self.expr_index + 1); + arm_end_ids.into_iter().for_each(|arm_end| { + // D -> H, G -> H self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1) }); } @@ -275,7 +414,7 @@ impl DropRangesBuilder { let mut tracked_value_map = FxHashMap::<_, TrackedValueIndex>::default(); let mut next = <_>::from(0u32); for value in tracked_values { - for_each_consumable(value, hir.find(value.hir_id()), |value| { + for_each_consumable(hir, value, |value| { if !tracked_value_map.contains_key(&value) { tracked_value_map.insert(value, next); next = next + 1; diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs index 845cd01a44eed..059a135a6fb65 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -85,11 +85,9 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { "consume {:?}; diag_expr_id={:?}, using parent {:?}", place_with_id, diag_expr_id, parent ); - // We do not currently support partial drops or reinits, so just ignore - // any places with projections. - if place_with_id.place.projections.is_empty() { - self.mark_consumed(parent, place_with_id.into()); - } + place_with_id + .try_into() + .map_or((), |tracked_value| self.mark_consumed(parent, tracked_value)); } fn borrow( @@ -98,7 +96,9 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { _diag_expr_id: HirId, _bk: rustc_middle::ty::BorrowKind, ) { - self.places.borrowed.insert(place_with_id.into()); + place_with_id + .try_into() + .map_or(false, |tracked_value| self.places.borrowed.insert(tracked_value)); } fn mutate( diff --git a/src/test/ui/generator/partial-drop.rs b/src/test/ui/generator/partial-drop.rs index c8c07ba41c7e8..36f6e78cb3bfe 100644 --- a/src/test/ui/generator/partial-drop.rs +++ b/src/test/ui/generator/partial-drop.rs @@ -15,7 +15,26 @@ fn main() { let guard = Bar { foo: Foo, x: 42 }; drop(guard.foo); yield; - }) + }); + + assert_send(|| { + //~^ ERROR generator cannot be sent between threads safely + // FIXME: it would be nice to make this work. + let guard = Bar { foo: Foo, x: 42 }; + drop(guard); + guard.foo = Foo; + guard.x = 23; + yield; + }); + + assert_send(|| { + //~^ ERROR generator cannot be sent between threads safely + // FIXME: it would be nice to make this work. + let guard = Bar { foo: Foo, x: 42 }; + let Bar { foo, x } = guard; + drop(foo); + yield; + }); } fn assert_send(_: T) {} diff --git a/src/test/ui/generator/partial-drop.stderr b/src/test/ui/generator/partial-drop.stderr index 93112f52208a5..9a1b0734d8c86 100644 --- a/src/test/ui/generator/partial-drop.stderr +++ b/src/test/ui/generator/partial-drop.stderr @@ -13,13 +13,59 @@ LL | let guard = Bar { foo: Foo, x: 42 }; LL | drop(guard.foo); LL | yield; | ^^^^^ yield occurs here, with `guard` maybe used later -LL | }) +LL | }); | - `guard` is later dropped here note: required by a bound in `assert_send` - --> $DIR/partial-drop.rs:21:19 + --> $DIR/partial-drop.rs:40:19 | LL | fn assert_send(_: T) {} | ^^^^ required by this bound in `assert_send` -error: aborting due to previous error +error: generator cannot be sent between threads safely + --> $DIR/partial-drop.rs:20:5 + | +LL | assert_send(|| { + | ^^^^^^^^^^^ generator is not `Send` + | + = help: within `[generator@$DIR/partial-drop.rs:20:17: 28:6]`, the trait `Send` is not implemented for `Foo` +note: generator is not `Send` as this value is used across a yield + --> $DIR/partial-drop.rs:27:9 + | +LL | let guard = Bar { foo: Foo, x: 42 }; + | ----- has type `Bar` which is not `Send` +... +LL | yield; + | ^^^^^ yield occurs here, with `guard` maybe used later +LL | }); + | - `guard` is later dropped here +note: required by a bound in `assert_send` + --> $DIR/partial-drop.rs:40:19 + | +LL | fn assert_send(_: T) {} + | ^^^^ required by this bound in `assert_send` + +error: generator cannot be sent between threads safely + --> $DIR/partial-drop.rs:30:5 + | +LL | assert_send(|| { + | ^^^^^^^^^^^ generator is not `Send` + | + = help: within `[generator@$DIR/partial-drop.rs:30:17: 37:6]`, the trait `Send` is not implemented for `Foo` +note: generator is not `Send` as this value is used across a yield + --> $DIR/partial-drop.rs:36:9 + | +LL | let guard = Bar { foo: Foo, x: 42 }; + | ----- has type `Bar` which is not `Send` +... +LL | yield; + | ^^^^^ yield occurs here, with `guard` maybe used later +LL | }); + | - `guard` is later dropped here +note: required by a bound in `assert_send` + --> $DIR/partial-drop.rs:40:19 + | +LL | fn assert_send(_: T) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to 3 previous errors From d840d0c62e3853f9f3569315ffff666c4452718a Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 18 Jan 2022 14:02:42 -0800 Subject: [PATCH 39/69] Use .. patterns in cfg_build.rs --- .../drop_ranges/cfg_build.rs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index d7305957f94f8..fc150e09484b6 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -163,36 +163,36 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { // // Some of these may be interesting in the future ExprKind::Path(..) - | ExprKind::Box(_) - | ExprKind::ConstBlock(_) - | ExprKind::Array(_) - | ExprKind::Call(_, _) - | ExprKind::MethodCall(_, _, _, _) - | ExprKind::Tup(_) - | ExprKind::Binary(_, _, _) - | ExprKind::Unary(_, _) - | ExprKind::Lit(_) - | ExprKind::Cast(_, _) - | ExprKind::Type(_, _) - | ExprKind::DropTemps(_) - | ExprKind::Let(_, _, _) - | ExprKind::If(_, _, _) - | ExprKind::Loop(_, _, _, _) - | ExprKind::Match(_, _, _) - | ExprKind::Closure(_, _, _, _, _) - | ExprKind::Block(_, _) - | ExprKind::Assign(_, _, _) - | ExprKind::AssignOp(_, _, _) - | ExprKind::Index(_, _) - | ExprKind::AddrOf(_, _, _) - | ExprKind::Break(_, _) - | ExprKind::Continue(_) - | ExprKind::Ret(_) - | ExprKind::InlineAsm(_) - | ExprKind::LlvmInlineAsm(_) - | ExprKind::Struct(_, _, _) - | ExprKind::Repeat(_, _) - | ExprKind::Yield(_, _) + | ExprKind::Box(..) + | ExprKind::ConstBlock(..) + | ExprKind::Array(..) + | ExprKind::Call(..) + | ExprKind::MethodCall(..) + | ExprKind::Tup(..) + | ExprKind::Binary(..) + | ExprKind::Unary(..) + | ExprKind::Lit(..) + | ExprKind::Cast(..) + | ExprKind::Type(..) + | ExprKind::DropTemps(..) + | ExprKind::Let(..) + | ExprKind::If(..) + | ExprKind::Loop(..) + | ExprKind::Match(..) + | ExprKind::Closure(..) + | ExprKind::Block(..) + | ExprKind::Assign(..) + | ExprKind::AssignOp(..) + | ExprKind::Index(..) + | ExprKind::AddrOf(..) + | ExprKind::Break(..) + | ExprKind::Continue(..) + | ExprKind::Ret(..) + | ExprKind::InlineAsm(..) + | ExprKind::LlvmInlineAsm(..) + | ExprKind::Struct(..) + | ExprKind::Repeat(..) + | ExprKind::Yield(..) | ExprKind::Err => (), } } From 76f6b57125bd9394ad3ce8c241d2eebc4f99042b Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 18 Jan 2022 14:42:39 -0800 Subject: [PATCH 40/69] Fix build after rebase --- .../check/generator_interior/drop_ranges/cfg_build.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index fc150e09484b6..fc957b899909d 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -3,7 +3,7 @@ use super::{ NodeInfo, PostOrderId, TrackedValue, TrackedValueIndex, }; use hir::{ - intravisit::{self, NestedVisitorMap, Visitor}, + intravisit::{self, Visitor}, Body, Expr, ExprKind, Guard, HirId, }; use rustc_data_structures::fx::FxHashMap; @@ -189,7 +189,6 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::InlineAsm(..) - | ExprKind::LlvmInlineAsm(..) | ExprKind::Struct(..) | ExprKind::Repeat(..) | ExprKind::Yield(..) @@ -213,12 +212,6 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { } impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { - type Map = intravisit::ErasedMap<'tcx>; - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut reinit = None; match expr.kind { @@ -378,7 +371,6 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { | ExprKind::InlineAsm(..) | ExprKind::Let(..) | ExprKind::Lit(..) - | ExprKind::LlvmInlineAsm(..) | ExprKind::Path(..) | ExprKind::Repeat(..) | ExprKind::Ret(..) From 017747fa5afb1e5396582498303c8437792966d7 Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Wed, 19 Jan 2022 02:27:15 +0000 Subject: [PATCH 41/69] Only suggest adding `!` to expressions that can be macro invocation --- compiler/rustc_resolve/src/late.rs | 4 ++ .../rustc_resolve/src/late/diagnostics.rs | 11 ++++- .../hygiene/rustc-macro-transparency.stderr | 28 ++++-------- src/test/ui/resolve/resolve-hint-macro.fixed | 11 +++++ src/test/ui/resolve/resolve-hint-macro.rs | 7 +++ src/test/ui/resolve/resolve-hint-macro.stderr | 45 +++++++++++++++++-- 6 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 src/test/ui/resolve/resolve-hint-macro.fixed diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index ccaaa2eaf4672..2c678e71ae179 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -2517,6 +2517,10 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { self.visit_expr(elem); self.resolve_anon_const(ct, IsRepeatExpr::Yes); } + ExprKind::Index(ref elem, ref idx) => { + self.resolve_expr(elem, Some(expr)); + self.visit_expr(idx); + } _ => { visit::walk_expr(self, expr); } diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 4cd1b34bedc95..7b4fe6f0e07b3 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -970,7 +970,13 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> { }; match (res, source) { - (Res::Def(DefKind::Macro(MacroKind::Bang), _), _) => { + ( + Res::Def(DefKind::Macro(MacroKind::Bang), _), + PathSource::Expr(Some(Expr { + kind: ExprKind::Index(..) | ExprKind::Call(..), .. + })) + | PathSource::Struct, + ) => { err.span_label(span, fallback_label); err.span_suggestion_verbose( span.shrink_to_hi(), @@ -982,6 +988,9 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> { err.note("if you want the `try` keyword, you need Rust 2018 or later"); } } + (Res::Def(DefKind::Macro(MacroKind::Bang), _), _) => { + err.span_label(span, fallback_label); + } (Res::Def(DefKind::TyAlias, def_id), PathSource::Trait(_)) => { err.span_label(span, "type aliases cannot be used as traits"); if self.r.session.is_nightly_build() { diff --git a/src/test/ui/hygiene/rustc-macro-transparency.stderr b/src/test/ui/hygiene/rustc-macro-transparency.stderr index e4c1c8ad293b7..17d05dd09636c 100644 --- a/src/test/ui/hygiene/rustc-macro-transparency.stderr +++ b/src/test/ui/hygiene/rustc-macro-transparency.stderr @@ -11,16 +11,10 @@ LL | struct SemiTransparent; | ----------------------- similarly named unit struct `SemiTransparent` defined here ... LL | semitransparent; - | ^^^^^^^^^^^^^^^ not a value - | -help: use `!` to invoke the macro - | -LL | semitransparent!; - | + -help: a unit struct with a similar name exists - | -LL | SemiTransparent; - | ~~~~~~~~~~~~~~~ + | ^^^^^^^^^^^^^^^ + | | + | not a value + | help: a unit struct with a similar name exists: `SemiTransparent` error[E0423]: expected value, found macro `opaque` --> $DIR/rustc-macro-transparency.rs:30:5 @@ -29,16 +23,10 @@ LL | struct Opaque; | -------------- similarly named unit struct `Opaque` defined here ... LL | opaque; - | ^^^^^^ not a value - | -help: use `!` to invoke the macro - | -LL | opaque!; - | + -help: a unit struct with a similar name exists - | -LL | Opaque; - | ~~~~~~ + | ^^^^^^ + | | + | not a value + | help: a unit struct with a similar name exists (notice the capitalization): `Opaque` error: aborting due to 3 previous errors diff --git a/src/test/ui/resolve/resolve-hint-macro.fixed b/src/test/ui/resolve/resolve-hint-macro.fixed new file mode 100644 index 0000000000000..54e01608498f8 --- /dev/null +++ b/src/test/ui/resolve/resolve-hint-macro.fixed @@ -0,0 +1,11 @@ +// run-rustfix +fn main() { + assert_eq!(1, 1); + //~^ ERROR expected function, found macro `assert_eq` + assert_eq! { 1, 1 }; + //~^ ERROR expected struct, variant or union type, found macro `assert_eq` + //~| ERROR expected identifier, found `1` + //~| ERROR expected identifier, found `1` + assert![true]; + //~^ ERROR expected value, found macro `assert` +} diff --git a/src/test/ui/resolve/resolve-hint-macro.rs b/src/test/ui/resolve/resolve-hint-macro.rs index 5532c8b90e326..f16e8c0755384 100644 --- a/src/test/ui/resolve/resolve-hint-macro.rs +++ b/src/test/ui/resolve/resolve-hint-macro.rs @@ -1,4 +1,11 @@ +// run-rustfix fn main() { assert_eq(1, 1); //~^ ERROR expected function, found macro `assert_eq` + assert_eq { 1, 1 }; + //~^ ERROR expected struct, variant or union type, found macro `assert_eq` + //~| ERROR expected identifier, found `1` + //~| ERROR expected identifier, found `1` + assert[true]; + //~^ ERROR expected value, found macro `assert` } diff --git a/src/test/ui/resolve/resolve-hint-macro.stderr b/src/test/ui/resolve/resolve-hint-macro.stderr index 78d77678083c3..bc69ddd8ffea2 100644 --- a/src/test/ui/resolve/resolve-hint-macro.stderr +++ b/src/test/ui/resolve/resolve-hint-macro.stderr @@ -1,5 +1,21 @@ +error: expected identifier, found `1` + --> $DIR/resolve-hint-macro.rs:5:17 + | +LL | assert_eq { 1, 1 }; + | --------- ^ expected identifier + | | + | while parsing this struct + +error: expected identifier, found `1` + --> $DIR/resolve-hint-macro.rs:5:20 + | +LL | assert_eq { 1, 1 }; + | --------- ^ expected identifier + | | + | while parsing this struct + error[E0423]: expected function, found macro `assert_eq` - --> $DIR/resolve-hint-macro.rs:2:5 + --> $DIR/resolve-hint-macro.rs:3:5 | LL | assert_eq(1, 1); | ^^^^^^^^^ not a function @@ -9,6 +25,29 @@ help: use `!` to invoke the macro LL | assert_eq!(1, 1); | + -error: aborting due to previous error +error[E0574]: expected struct, variant or union type, found macro `assert_eq` + --> $DIR/resolve-hint-macro.rs:5:5 + | +LL | assert_eq { 1, 1 }; + | ^^^^^^^^^ not a struct, variant or union type + | +help: use `!` to invoke the macro + | +LL | assert_eq! { 1, 1 }; + | + + +error[E0423]: expected value, found macro `assert` + --> $DIR/resolve-hint-macro.rs:9:5 + | +LL | assert[true]; + | ^^^^^^ not a value + | +help: use `!` to invoke the macro + | +LL | assert![true]; + | + + +error: aborting due to 5 previous errors -For more information about this error, try `rustc --explain E0423`. +Some errors have detailed explanations: E0423, E0574. +For more information about an error, try `rustc --explain E0423`. From 10858d28af13351e8d959a8a577427b1580a7185 Mon Sep 17 00:00:00 2001 From: Richard Cobbe Date: Mon, 29 Nov 2021 11:28:41 -0800 Subject: [PATCH 42/69] Fix test directives; comment out calls broken on windows-gnu --- .../run-make/raw-dylib-alt-calling-convention/Makefile | 7 ++++++- src/test/run-make/raw-dylib-alt-calling-convention/lib.rs | 7 +++++-- .../run-make/raw-dylib-alt-calling-convention/output.txt | 2 -- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/test/run-make/raw-dylib-alt-calling-convention/Makefile b/src/test/run-make/raw-dylib-alt-calling-convention/Makefile index 0f874333fa09c..4af8b43ea848d 100644 --- a/src/test/run-make/raw-dylib-alt-calling-convention/Makefile +++ b/src/test/run-make/raw-dylib-alt-calling-convention/Makefile @@ -1,12 +1,17 @@ # Test the behavior of #[link(.., kind = "raw-dylib")] with alternative calling conventions. -# only-i686-pc-windows-msvc +# only-x86 +# only-windows -include ../../run-make-fulldeps/tools.mk all: $(call COMPILE_OBJ,"$(TMPDIR)"/extern.obj,extern.c) +ifdef IS_MSVC $(CC) "$(TMPDIR)"/extern.obj -link -dll -out:"$(TMPDIR)"/extern.dll +else + $(CC) "$(TMPDIR)"/extern.obj -shared -o "$(TMPDIR)"/extern.dll +endif $(RUSTC) --crate-type lib --crate-name raw_dylib_alt_calling_convention_test lib.rs $(RUSTC) --crate-type bin driver.rs -L "$(TMPDIR)" "$(TMPDIR)"/driver > "$(TMPDIR)"/output.txt diff --git a/src/test/run-make/raw-dylib-alt-calling-convention/lib.rs b/src/test/run-make/raw-dylib-alt-calling-convention/lib.rs index ba0f1418aba77..165792b049015 100644 --- a/src/test/run-make/raw-dylib-alt-calling-convention/lib.rs +++ b/src/test/run-make/raw-dylib-alt-calling-convention/lib.rs @@ -62,9 +62,12 @@ pub fn library_function() { fastcall_fn_2(16, 3.5); fastcall_fn_3(3.5); fastcall_fn_4(1, 2, 3.0); - fastcall_fn_5(S { x: 1, y: 2 }, 16); + // FIXME: 91167 + // rustc generates incorrect code for the calls to fastcall_fn_5 and fastcall_fn_7 + // on i686-pc-windows-gnu; commenting these out until the indicated issue is fixed. + //fastcall_fn_5(S { x: 1, y: 2 }, 16); fastcall_fn_6(Some(&S { x: 10, y: 12 })); - fastcall_fn_7(S2 { x: 15, y: 16 }, 3); + //fastcall_fn_7(S2 { x: 15, y: 16 }, 3); fastcall_fn_8(S3 { x: [1, 2, 3, 4, 5] }, S3 { x: [6, 7, 8, 9, 10] }); fastcall_fn_9(1, 3.0); } diff --git a/src/test/run-make/raw-dylib-alt-calling-convention/output.txt b/src/test/run-make/raw-dylib-alt-calling-convention/output.txt index be598a2202782..348bad63ed0de 100644 --- a/src/test/run-make/raw-dylib-alt-calling-convention/output.txt +++ b/src/test/run-make/raw-dylib-alt-calling-convention/output.txt @@ -11,8 +11,6 @@ fastcall_fn_1(14) fastcall_fn_2(16, 3.5) fastcall_fn_3(3.5) fastcall_fn_4(1, 2, 3.0) -fastcall_fn_5(S { x: 1, y: 2 }, 16) fastcall_fn_6(S { x: 10, y: 12 }) -fastcall_fn_7(S2 { x: 15, y: 16 }, 3) fastcall_fn_8(S3 { x: [1, 2, 3, 4, 5] }, S3 { x: [6, 7, 8, 9, 10] }) fastcall_fn_9(1, 3.0) From f491a9f601a8ad0efe7f38de5b807a2c07774ae1 Mon Sep 17 00:00:00 2001 From: Caio Date: Wed, 19 Jan 2022 16:23:44 -0300 Subject: [PATCH 43/69] Add tests to ensure that let_chains works with if_let_guard --- .../rfc-2497-if-let-chains/irrefutable-lets.rs | 12 +++++++++++- .../rfc-2497-if-let-chains/then-else-blocks.rs | 16 +++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/test/ui/rfc-2497-if-let-chains/irrefutable-lets.rs b/src/test/ui/rfc-2497-if-let-chains/irrefutable-lets.rs index 5915cb9df269c..945c665e35d28 100644 --- a/src/test/ui/rfc-2497-if-let-chains/irrefutable-lets.rs +++ b/src/test/ui/rfc-2497-if-let-chains/irrefutable-lets.rs @@ -1,6 +1,6 @@ // check-pass -#![feature(let_chains)] +#![feature(if_let_guard, let_chains)] use std::ops::Range; @@ -16,6 +16,16 @@ fn main() { && let None = local_start { } + match opt { + Some(ref first) if let second = first && let _third = second => {}, + _ => {} + } + match opt { + Some(ref first) if let Range { start: local_start, end: _ } = first + && let None = local_start => {}, + _ => {} + } + while let first = &opt && let Some(ref second) = first && let None = second.start { } while let Some(ref first) = opt && let second = first && let _third = second { diff --git a/src/test/ui/rfc-2497-if-let-chains/then-else-blocks.rs b/src/test/ui/rfc-2497-if-let-chains/then-else-blocks.rs index 0856a10520636..e061174f667d9 100644 --- a/src/test/ui/rfc-2497-if-let-chains/then-else-blocks.rs +++ b/src/test/ui/rfc-2497-if-let-chains/then-else-blocks.rs @@ -1,6 +1,6 @@ // run-pass -#![feature(let_chains)] +#![feature(if_let_guard, let_chains)] fn check_if_let(opt: Option>>, value: i32) -> bool { if let Some(first) = opt @@ -15,6 +15,17 @@ fn check_if_let(opt: Option>>, value: i32) -> bool { } } +fn check_let_guard(opt: Option>>, value: i32) -> bool { + match opt { + Some(first) if let Some(second) = first && let Some(third) = second && third == value => { + true + } + _ => { + false + } + } +} + fn check_while_let(opt: Option>>, value: i32) -> bool { while let Some(first) = opt && let Some(second) = first @@ -30,6 +41,9 @@ fn main() { assert_eq!(check_if_let(Some(Some(Some(1))), 1), true); assert_eq!(check_if_let(Some(Some(Some(1))), 9), false); + assert_eq!(check_let_guard(Some(Some(Some(1))), 1), true); + assert_eq!(check_let_guard(Some(Some(Some(1))), 9), false); + assert_eq!(check_while_let(Some(Some(Some(1))), 1), true); assert_eq!(check_while_let(Some(Some(Some(1))), 9), false); } From 8d27c28e39fa141432dde2d3b2e353f31154e72f Mon Sep 17 00:00:00 2001 From: pierwill Date: Wed, 19 Jan 2022 13:44:43 -0600 Subject: [PATCH 44/69] =?UTF-8?q?=E2=AC=86=20chalk=20to=200.76.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 17 +++++++++-------- compiler/rustc_middle/Cargo.toml | 2 +- compiler/rustc_traits/Cargo.toml | 6 +++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73ffd3e044eb3..b5b9b50e58d84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -542,9 +542,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chalk-derive" -version = "0.75.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54e3b5f9e3425e6b119ff07568d8d006bfa5a8d6f78a9cbc3530b1e962e316c" +checksum = "58c24b8052ea1e3adbb6f9ab7ba5fcc18b9d12591c042de4c833f709ce81e0e0" dependencies = [ "proc-macro2", "quote", @@ -554,9 +554,9 @@ dependencies = [ [[package]] name = "chalk-engine" -version = "0.75.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdc891073396b167163db77123b0a3c00088edc00466cecc5531f33e3e989523" +checksum = "0eca186b6ea9af798312f4b568fd094c82e7946ac08be5dc5fea22decc6d2ed8" dependencies = [ "chalk-derive", "chalk-ir", @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "chalk-ir" -version = "0.75.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b79e5a1d04b79311e90c69356a2c62027853906a7e33b3e070b93c055fc3e8a" +checksum = "f3cad5c3f1edd4b4a2c9bda24ae558ceb4f88336f88f944c2e35d0bfeb13c818" dependencies = [ "bitflags", "chalk-derive", @@ -578,13 +578,14 @@ dependencies = [ [[package]] name = "chalk-solve" -version = "0.75.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d2a1db6605aba70a58820bd80ac422b218913a510f1a40beef9efc5371ea1d" +checksum = "94533188d3452bc72cbd5618d166f45fc7646b674ad3fe9667d557bc25236dee" dependencies = [ "chalk-derive", "chalk-ir", "ena", + "indexmap", "itertools 0.10.1", "petgraph", "rustc-hash", diff --git a/compiler/rustc_middle/Cargo.toml b/compiler/rustc_middle/Cargo.toml index 30664784ed8f8..b1334410237e7 100644 --- a/compiler/rustc_middle/Cargo.toml +++ b/compiler/rustc_middle/Cargo.toml @@ -29,7 +29,7 @@ rustc_index = { path = "../rustc_index" } rustc_serialize = { path = "../rustc_serialize" } rustc_ast = { path = "../rustc_ast" } rustc_span = { path = "../rustc_span" } -chalk-ir = "0.75.0" +chalk-ir = "0.76.0" smallvec = { version = "1.6.1", features = ["union", "may_dangle"] } rustc_session = { path = "../rustc_session" } rustc_type_ir = { path = "../rustc_type_ir" } diff --git a/compiler/rustc_traits/Cargo.toml b/compiler/rustc_traits/Cargo.toml index f22751dc740c9..25f228c789031 100644 --- a/compiler/rustc_traits/Cargo.toml +++ b/compiler/rustc_traits/Cargo.toml @@ -12,9 +12,9 @@ rustc_hir = { path = "../rustc_hir" } rustc_index = { path = "../rustc_index" } rustc_ast = { path = "../rustc_ast" } rustc_span = { path = "../rustc_span" } -chalk-ir = "0.75.0" -chalk-engine = "0.75.0" -chalk-solve = "0.75.0" +chalk-ir = "0.76.0" +chalk-engine = "0.76.0" +chalk-solve = "0.76.0" smallvec = { version = "1.6.1", features = ["union", "may_dangle"] } rustc_infer = { path = "../rustc_infer" } rustc_trait_selection = { path = "../rustc_trait_selection" } From 801ac0e24ff6269fe71d22d29b8482e2117b2f82 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Tue, 18 Jan 2022 22:05:17 -0800 Subject: [PATCH 45/69] Fix scroll offset when jumping to internal id --- src/librustdoc/html/static/css/rustdoc.css | 9 ++++++++- src/test/rustdoc-gui/sidebar-mobile.goml | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index dbc068ce6b13b..947eb5a37f2a7 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1738,12 +1738,19 @@ details.rustdoc-toggle[open] > summary.hideme::after { } @media (max-width: 700px) { + /* When linking to an item with an `id` (for instance, by clicking a link in the sidebar, + or visiting a URL with a fragment like `#method.new`, we don't want the item to be obscured + by the topbar. Anything with an `id` gets scroll-margin-top equal to .mobile-topbar's size. + */ + *[id] { + scroll-margin-top: 45px; + } + .rustdoc { padding-top: 0px; /* Sidebar should overlay main content, rather than pushing main content to the right. Turn off `display: flex` on the body element. */ display: block; - scroll-margin-top: 45px; } main { diff --git a/src/test/rustdoc-gui/sidebar-mobile.goml b/src/test/rustdoc-gui/sidebar-mobile.goml index 547eb3fd1b3d1..680822b6ecb8c 100644 --- a/src/test/rustdoc-gui/sidebar-mobile.goml +++ b/src/test/rustdoc-gui/sidebar-mobile.goml @@ -29,3 +29,9 @@ assert-css: (".sidebar", {"display": "block", "left": "-1000px"}) // Check that the topbar is visible assert-property: (".mobile-topbar", {"clientHeight": "45"}) + +// Check that clicking an element from the sidebar scrolls to the right place +// so the target is not obscured by the topbar. +click: ".sidebar-menu-toggle" +click: ".sidebar-links a" +assert-position: ("#method\.must_use", {"y": 45}) From ab239cc7499c137cc6d4b3b6e9a01b78f7bd3b07 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Wed, 19 Jan 2022 22:03:08 +0100 Subject: [PATCH 46/69] src/test/rustdoc-json: Check for `struct_field`s in `variant_tuple_struct.rs` The presence of `struct_field`s is being checked for already in `variant_struct.rs`. We should also check for them in `variant_tuple_struct.rs`. --- src/test/rustdoc-json/enums/variant_struct.rs | 4 ++-- src/test/rustdoc-json/enums/variant_tuple_struct.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/rustdoc-json/enums/variant_struct.rs b/src/test/rustdoc-json/enums/variant_struct.rs index 246e6a09007b3..fcd92887c0bfb 100644 --- a/src/test/rustdoc-json/enums/variant_struct.rs +++ b/src/test/rustdoc-json/enums/variant_struct.rs @@ -2,8 +2,8 @@ // @has - "$.index[*][?(@.name=='EnumStruct')].kind" \"enum\" pub enum EnumStruct { // @has - "$.index[*][?(@.name=='VariantS')].inner.variant_kind" \"struct\" - // @has - "$.index[*][?(@.name=='x')]" - // @has - "$.index[*][?(@.name=='y')]" + // @has - "$.index[*][?(@.name=='x')].kind" \"struct_field\" + // @has - "$.index[*][?(@.name=='y')].kind" \"struct_field\" VariantS { x: u32, y: String, diff --git a/src/test/rustdoc-json/enums/variant_tuple_struct.rs b/src/test/rustdoc-json/enums/variant_tuple_struct.rs index d948dc552cdbc..ac3e72e2905d2 100644 --- a/src/test/rustdoc-json/enums/variant_tuple_struct.rs +++ b/src/test/rustdoc-json/enums/variant_tuple_struct.rs @@ -2,5 +2,7 @@ // @has - "$.index[*][?(@.name=='EnumTupleStruct')].kind" \"enum\" pub enum EnumTupleStruct { // @has - "$.index[*][?(@.name=='VariantA')].inner.variant_kind" \"tuple\" + // @has - "$.index[*][?(@.name=='0')].kind" \"struct_field\" + // @has - "$.index[*][?(@.name=='1')].kind" \"struct_field\" VariantA(u32, String), } From 70d36a05bc79667578a3c7b2b4926e21a9a5d013 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 19 Jan 2022 17:36:44 -0500 Subject: [PATCH 47/69] Show a more informative panic message when `DefPathHash` does not exist This should hopefully make it easier to debug incremental compilation bugs like #93096 without affecting performance. --- compiler/rustc_hir/src/definitions.rs | 8 ++++++-- compiler/rustc_middle/src/dep_graph/dep_node.rs | 4 +++- compiler/rustc_middle/src/ty/context.rs | 7 +++++-- compiler/rustc_query_impl/src/on_disk_cache.rs | 4 +++- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_hir/src/definitions.rs b/compiler/rustc_hir/src/definitions.rs index d813c887eee9a..e839f7fc7779a 100644 --- a/compiler/rustc_hir/src/definitions.rs +++ b/compiler/rustc_hir/src/definitions.rs @@ -449,13 +449,17 @@ impl Definitions { } #[inline(always)] - pub fn local_def_path_hash_to_def_id(&self, hash: DefPathHash) -> LocalDefId { + pub fn local_def_path_hash_to_def_id( + &self, + hash: DefPathHash, + err: &mut dyn FnMut() -> !, + ) -> LocalDefId { debug_assert!(hash.stable_crate_id() == self.stable_crate_id); self.table .def_path_hash_to_index .get(&hash) .map(|local_def_index| LocalDefId { local_def_index }) - .unwrap() + .unwrap_or_else(|| err()) } pub fn def_path_hash_to_def_index_map(&self) -> &DefPathHashMap { diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs index 5c7cdbe4c2bdb..d20be0a34d2d5 100644 --- a/compiler/rustc_middle/src/dep_graph/dep_node.rs +++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs @@ -266,7 +266,9 @@ impl DepNodeExt for DepNode { /// has been removed. fn extract_def_id<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Option { if self.kind.fingerprint_style(tcx) == FingerprintStyle::DefPathHash { - Some(tcx.def_path_hash_to_def_id(DefPathHash(self.hash.into()))) + Some(tcx.def_path_hash_to_def_id(DefPathHash(self.hash.into()), &mut || { + panic!("Failed to extract DefId: {:?} {}", self.kind, self.hash) + })) } else { None } diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index e7b99995ca4ae..b493ff1620380 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -1308,7 +1308,7 @@ impl<'tcx> TyCtxt<'tcx> { /// Converts a `DefPathHash` to its corresponding `DefId` in the current compilation /// session, if it still exists. This is used during incremental compilation to /// turn a deserialized `DefPathHash` into its current `DefId`. - pub fn def_path_hash_to_def_id(self, hash: DefPathHash) -> DefId { + pub fn def_path_hash_to_def_id(self, hash: DefPathHash, err: &mut dyn FnMut() -> !) -> DefId { debug!("def_path_hash_to_def_id({:?})", hash); let stable_crate_id = hash.stable_crate_id(); @@ -1316,7 +1316,10 @@ impl<'tcx> TyCtxt<'tcx> { // If this is a DefPathHash from the local crate, we can look up the // DefId in the tcx's `Definitions`. if stable_crate_id == self.sess.local_stable_crate_id() { - self.untracked_resolutions.definitions.local_def_path_hash_to_def_id(hash).to_def_id() + self.untracked_resolutions + .definitions + .local_def_path_hash_to_def_id(hash, err) + .to_def_id() } else { // If this is a DefPathHash from an upstream crate, let the CrateStore map // it to a DefId. diff --git a/compiler/rustc_query_impl/src/on_disk_cache.rs b/compiler/rustc_query_impl/src/on_disk_cache.rs index 6a88e1235374c..5f6d9b050b2e5 100644 --- a/compiler/rustc_query_impl/src/on_disk_cache.rs +++ b/compiler/rustc_query_impl/src/on_disk_cache.rs @@ -761,7 +761,9 @@ impl<'a, 'tcx> Decodable> for DefId { // If we get to this point, then all of the query inputs were green, // which means that the definition with this hash is guaranteed to // still exist in the current compilation session. - Ok(d.tcx().def_path_hash_to_def_id(def_path_hash)) + Ok(d.tcx().def_path_hash_to_def_id(def_path_hash, &mut || { + panic!("Failed to convert DefPathHash {:?}", def_path_hash) + })) } } From eec6016ec3e0cc6be3ae75007586b6a56372b382 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:26:16 -0800 Subject: [PATCH 48/69] Delete unused Display for pretty printer Token --- compiler/rustc_ast_pretty/src/pp.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index bdd70148d85a0..8b7ea5d792dfb 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -137,7 +137,6 @@ mod ring; use ring::RingBuffer; use std::borrow::Cow; use std::collections::VecDeque; -use std::fmt; /// How to break. Described in more detail in the module docs. #[derive(Clone, Copy, PartialEq)] @@ -175,17 +174,6 @@ impl Token { } } -impl fmt::Display for Token { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Token::String(ref s) => write!(f, "STR({},{})", s, s.len()), - Token::Break(_) => f.write_str("BREAK"), - Token::Begin(_) => f.write_str("BEGIN"), - Token::End => f.write_str("END"), - } - } -} - #[derive(Copy, Clone)] enum PrintStackBreak { Fits, From d81740ed2a63d377a725b0fbf935c391f5c7eb5e Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:27:26 -0800 Subject: [PATCH 49/69] Grow scan_stack in the conventional direction The pretty printer algorithm involves 2 VecDeques: a ring-buffer of tokens and a deque of ring-buffer indices. Confusingly, those two deques were being grown in opposite directions for no good reason. Ring-buffer pushes would go on the "back" of the ring-buffer (i.e. higher indices) while scan_stack pushes would go on the "front" (i.e. lower indices). This commit flips the scan_stack accesses to grow the scan_stack and ring-buffer in the same direction, where push does the same operation as a Vec push i.e. inserting on the high-index end. --- compiler/rustc_ast_pretty/src/pp.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index 8b7ea5d792dfb..0e3e7909afb84 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -266,7 +266,7 @@ impl Printer { self.buf.clear(); } let right = self.buf.push(BufEntry { token: Token::Begin(b), size: -self.right_total }); - self.scan_stack.push_front(right); + self.scan_stack.push_back(right); } fn scan_end(&mut self) { @@ -274,7 +274,7 @@ impl Printer { self.print_end(); } else { let right = self.buf.push(BufEntry { token: Token::End, size: -1 }); - self.scan_stack.push_front(right); + self.scan_stack.push_back(right); } } @@ -287,7 +287,7 @@ impl Printer { self.check_stack(0); } let right = self.buf.push(BufEntry { token: Token::Break(b), size: -self.right_total }); - self.scan_stack.push_front(right); + self.scan_stack.push_back(right); self.right_total += b.blank_space; } @@ -304,8 +304,8 @@ impl Printer { fn check_stream(&mut self) { while self.right_total - self.left_total > self.space { - if *self.scan_stack.back().unwrap() == self.buf.index_of_first() { - self.scan_stack.pop_back().unwrap(); + if *self.scan_stack.front().unwrap() == self.buf.index_of_first() { + self.scan_stack.pop_front().unwrap(); self.buf.first_mut().unwrap().size = SIZE_INFINITY; } self.advance_left(); @@ -345,25 +345,25 @@ impl Printer { } fn check_stack(&mut self, mut k: usize) { - while let Some(&x) = self.scan_stack.front() { + while let Some(&x) = self.scan_stack.back() { let mut entry = &mut self.buf[x]; match entry.token { Token::Begin(_) => { if k == 0 { break; } - self.scan_stack.pop_front().unwrap(); + self.scan_stack.pop_back().unwrap(); entry.size += self.right_total; k -= 1; } Token::End => { // paper says + not =, but that makes no sense. - self.scan_stack.pop_front().unwrap(); + self.scan_stack.pop_back().unwrap(); entry.size = 1; k += 1; } _ => { - self.scan_stack.pop_front().unwrap(); + self.scan_stack.pop_back().unwrap(); entry.size += self.right_total; if k == 0 { break; From d981c5b354c40a6097c83a72173ae8a5569db2e1 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:35:02 -0800 Subject: [PATCH 50/69] Eliminate a token clone from advance_left --- compiler/rustc_ast_pretty/src/pp.rs | 3 +-- compiler/rustc_ast_pretty/src/pp/ring.rs | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index 0e3e7909afb84..b93463e99fd5c 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -319,7 +319,7 @@ impl Printer { let mut left_size = self.buf.first().unwrap().size; while left_size >= 0 { - let left = self.buf.first().unwrap().token.clone(); + let left = self.buf.pop_first().unwrap().token; let len = match left { Token::Break(b) => b.blank_space, @@ -335,7 +335,6 @@ impl Printer { self.left_total += len; - self.buf.advance_left(); if self.buf.is_empty() { break; } diff --git a/compiler/rustc_ast_pretty/src/pp/ring.rs b/compiler/rustc_ast_pretty/src/pp/ring.rs index d20142eb591fe..8187394fe30e0 100644 --- a/compiler/rustc_ast_pretty/src/pp/ring.rs +++ b/compiler/rustc_ast_pretty/src/pp/ring.rs @@ -32,11 +32,6 @@ impl RingBuffer { index } - pub fn advance_left(&mut self) { - self.data.pop_front().unwrap(); - self.offset += 1; - } - pub fn clear(&mut self) { self.data.clear(); } @@ -53,6 +48,12 @@ impl RingBuffer { self.data.front_mut() } + pub fn pop_first(&mut self) -> Option { + let first = self.data.pop_front()?; + self.offset += 1; + Some(first) + } + pub fn last(&self) -> Option<&T> { self.data.back() } From 351011ec3f043ff17e53f92a2f05e3e58e8e2bf6 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:36:29 -0800 Subject: [PATCH 51/69] Simplify left_total tracking --- compiler/rustc_ast_pretty/src/pp.rs | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index b93463e99fd5c..583bdc616cb91 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -321,20 +321,14 @@ impl Printer { while left_size >= 0 { let left = self.buf.pop_first().unwrap().token; - let len = match left { - Token::Break(b) => b.blank_space, - Token::String(ref s) => { - let len = s.len() as isize; - assert_eq!(len, left_size); - len - } - _ => 0, - }; + match &left { + Token::Break(b) => self.left_total += b.blank_space, + Token::String(s) => self.left_total += s.len() as isize, + _ => {} + } self.print(left, left_size); - self.left_total += len; - if self.buf.is_empty() { break; } @@ -447,11 +441,7 @@ impl Printer { Token::Begin(b) => self.print_begin(*b, l), Token::End => self.print_end(), Token::Break(b) => self.print_break(*b, l), - Token::String(s) => { - let len = s.len() as isize; - assert_eq!(len, l); - self.print_string(s); - } + Token::String(s) => self.print_string(s), } self.last_printed = Some(token); } From d2eb46cfecf62210ee313da76acadedd7a2cbfcb Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:37:45 -0800 Subject: [PATCH 52/69] Simplify advance_left --- compiler/rustc_ast_pretty/src/pp.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index 583bdc616cb91..2c7962e44d05c 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -316,24 +316,20 @@ impl Printer { } fn advance_left(&mut self) { - let mut left_size = self.buf.first().unwrap().size; + while self.buf.first().unwrap().size >= 0 { + let left = self.buf.pop_first().unwrap(); - while left_size >= 0 { - let left = self.buf.pop_first().unwrap().token; - - match &left { + match &left.token { Token::Break(b) => self.left_total += b.blank_space, Token::String(s) => self.left_total += s.len() as isize, _ => {} } - self.print(left, left_size); + self.print(left.token, left.size); if self.buf.is_empty() { break; } - - left_size = self.buf.first().unwrap().size; } } From ae75ba692a456216139abe42e88b4ca45b973127 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:39:38 -0800 Subject: [PATCH 53/69] Inline print into advance_left --- compiler/rustc_ast_pretty/src/pp.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index 2c7962e44d05c..1a6a7f2c52a71 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -325,7 +325,14 @@ impl Printer { _ => {} } - self.print(left.token, left.size); + match &left.token { + Token::Begin(b) => self.print_begin(*b, left.size), + Token::End => self.print_end(), + Token::Break(b) => self.print_break(*b, left.size), + Token::String(s) => self.print_string(s), + } + + self.last_printed = Some(left.token); if self.buf.is_empty() { break; @@ -432,16 +439,6 @@ impl Printer { self.out.push_str(s); } - fn print(&mut self, token: Token, l: isize) { - match &token { - Token::Begin(b) => self.print_begin(*b, l), - Token::End => self.print_end(), - Token::Break(b) => self.print_break(*b, l), - Token::String(s) => self.print_string(s), - } - self.last_printed = Some(token); - } - // Convenience functions to talk to the printer. /// "raw box" From ea23a1fac7d053a05c2d38bfb48e1692a4dc90a3 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:41:22 -0800 Subject: [PATCH 54/69] Combine advance_left matches --- compiler/rustc_ast_pretty/src/pp.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index 1a6a7f2c52a71..e1234d67ec418 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -320,16 +320,16 @@ impl Printer { let left = self.buf.pop_first().unwrap(); match &left.token { - Token::Break(b) => self.left_total += b.blank_space, - Token::String(s) => self.left_total += s.len() as isize, - _ => {} - } - - match &left.token { + Token::String(s) => { + self.left_total += s.len() as isize; + self.print_string(s); + } + Token::Break(b) => { + self.left_total += b.blank_space; + self.print_break(*b, left.size); + } Token::Begin(b) => self.print_begin(*b, left.size), Token::End => self.print_end(), - Token::Break(b) => self.print_break(*b, left.size), - Token::String(s) => self.print_string(s), } self.last_printed = Some(left.token); From d5f15a8c18a80cce04641e4801deee94b3a0bf45 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:44:17 -0800 Subject: [PATCH 55/69] Replace all single character variable names --- compiler/rustc_ast_pretty/src/pp.rs | 96 +++++++++++++++-------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index e1234d67ec418..dadfac06847f4 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -248,8 +248,8 @@ impl Printer { } /// Be very careful with this! - pub fn replace_last_token_still_buffered(&mut self, t: Token) { - self.buf.last_mut().unwrap().token = t; + pub fn replace_last_token_still_buffered(&mut self, token: Token) { + self.buf.last_mut().unwrap().token = token; } fn scan_eof(&mut self) { @@ -259,13 +259,13 @@ impl Printer { } } - fn scan_begin(&mut self, b: BeginToken) { + fn scan_begin(&mut self, token: BeginToken) { if self.scan_stack.is_empty() { self.left_total = 1; self.right_total = 1; self.buf.clear(); } - let right = self.buf.push(BufEntry { token: Token::Begin(b), size: -self.right_total }); + let right = self.buf.push(BufEntry { token: Token::Begin(token), size: -self.right_total }); self.scan_stack.push_back(right); } @@ -278,7 +278,7 @@ impl Printer { } } - fn scan_break(&mut self, b: BreakToken) { + fn scan_break(&mut self, token: BreakToken) { if self.scan_stack.is_empty() { self.left_total = 1; self.right_total = 1; @@ -286,17 +286,17 @@ impl Printer { } else { self.check_stack(0); } - let right = self.buf.push(BufEntry { token: Token::Break(b), size: -self.right_total }); + let right = self.buf.push(BufEntry { token: Token::Break(token), size: -self.right_total }); self.scan_stack.push_back(right); - self.right_total += b.blank_space; + self.right_total += token.blank_space; } - fn scan_string(&mut self, s: Cow<'static, str>) { + fn scan_string(&mut self, string: Cow<'static, str>) { if self.scan_stack.is_empty() { - self.print_string(&s); + self.print_string(&string); } else { - let len = s.len() as isize; - self.buf.push(BufEntry { token: Token::String(s), size: len }); + let len = string.len() as isize; + self.buf.push(BufEntry { token: Token::String(string), size: len }); self.right_total += len; self.check_stream(); } @@ -320,15 +320,15 @@ impl Printer { let left = self.buf.pop_first().unwrap(); match &left.token { - Token::String(s) => { - self.left_total += s.len() as isize; - self.print_string(s); + Token::String(string) => { + self.left_total += string.len() as isize; + self.print_string(string); } - Token::Break(b) => { - self.left_total += b.blank_space; - self.print_break(*b, left.size); + Token::Break(token) => { + self.left_total += token.blank_space; + self.print_break(*token, left.size); } - Token::Begin(b) => self.print_begin(*b, left.size), + Token::Begin(token) => self.print_begin(*token, left.size), Token::End => self.print_end(), } @@ -340,28 +340,28 @@ impl Printer { } } - fn check_stack(&mut self, mut k: usize) { - while let Some(&x) = self.scan_stack.back() { - let mut entry = &mut self.buf[x]; + fn check_stack(&mut self, mut depth: usize) { + while let Some(&index) = self.scan_stack.back() { + let mut entry = &mut self.buf[index]; match entry.token { Token::Begin(_) => { - if k == 0 { + if depth == 0 { break; } self.scan_stack.pop_back().unwrap(); entry.size += self.right_total; - k -= 1; + depth -= 1; } Token::End => { // paper says + not =, but that makes no sense. self.scan_stack.pop_back().unwrap(); entry.size = 1; - k += 1; + depth += 1; } _ => { self.scan_stack.pop_back().unwrap(); entry.size += self.right_total; - if k == 0 { + if depth == 0 { break; } } @@ -385,11 +385,13 @@ impl Printer { }) } - fn print_begin(&mut self, b: BeginToken, l: isize) { - if l > self.space { - let col = self.margin - self.space + b.offset; - self.print_stack - .push(PrintStackElem { offset: col, pbreak: PrintStackBreak::Broken(b.breaks) }); + fn print_begin(&mut self, token: BeginToken, size: isize) { + if size > self.space { + let col = self.margin - self.space + token.offset; + self.print_stack.push(PrintStackElem { + offset: col, + pbreak: PrintStackBreak::Broken(token.breaks), + }); } else { self.print_stack.push(PrintStackElem { offset: 0, pbreak: PrintStackBreak::Fits }); } @@ -399,31 +401,31 @@ impl Printer { self.print_stack.pop().unwrap(); } - fn print_break(&mut self, b: BreakToken, l: isize) { + fn print_break(&mut self, token: BreakToken, size: isize) { let top = self.get_top(); match top.pbreak { PrintStackBreak::Fits => { - self.space -= b.blank_space; - self.indent(b.blank_space); + self.space -= token.blank_space; + self.indent(token.blank_space); } PrintStackBreak::Broken(Breaks::Consistent) => { - self.print_newline(top.offset + b.offset); - self.space = self.margin - (top.offset + b.offset); + self.print_newline(top.offset + token.offset); + self.space = self.margin - (top.offset + token.offset); } PrintStackBreak::Broken(Breaks::Inconsistent) => { - if l > self.space { - self.print_newline(top.offset + b.offset); - self.space = self.margin - (top.offset + b.offset); + if size > self.space { + self.print_newline(top.offset + token.offset); + self.space = self.margin - (top.offset + token.offset); } else { - self.indent(b.blank_space); - self.space -= b.blank_space; + self.indent(token.blank_space); + self.space -= token.blank_space; } } } } - fn print_string(&mut self, s: &str) { - let len = s.len() as isize; + fn print_string(&mut self, string: &str) { + let len = string.len() as isize; // assert!(len <= space); self.space -= len; @@ -436,14 +438,14 @@ impl Printer { self.out.reserve(self.pending_indentation as usize); self.out.extend(std::iter::repeat(' ').take(self.pending_indentation as usize)); self.pending_indentation = 0; - self.out.push_str(s); + self.out.push_str(string); } // Convenience functions to talk to the printer. /// "raw box" - pub fn rbox(&mut self, indent: usize, b: Breaks) { - self.scan_begin(BeginToken { offset: indent as isize, breaks: b }) + pub fn rbox(&mut self, indent: usize, breaks: Breaks) { + self.scan_begin(BeginToken { offset: indent as isize, breaks }) } /// Inconsistent breaking box @@ -470,8 +472,8 @@ impl Printer { } pub fn word>>(&mut self, wrd: S) { - let s = wrd.into(); - self.scan_string(s) + let string = wrd.into(); + self.scan_string(string) } fn spaces(&mut self, n: usize) { From 65dd67096e6771a3bdf5b9b4a4cd638777a0ae89 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:46:49 -0800 Subject: [PATCH 56/69] Touch up print_string --- compiler/rustc_ast_pretty/src/pp.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index dadfac06847f4..11114b5322077 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -137,6 +137,7 @@ mod ring; use ring::RingBuffer; use std::borrow::Cow; use std::collections::VecDeque; +use std::iter; /// How to break. Described in more detail in the module docs. #[derive(Clone, Copy, PartialEq)] @@ -425,10 +426,6 @@ impl Printer { } fn print_string(&mut self, string: &str) { - let len = string.len() as isize; - // assert!(len <= space); - self.space -= len; - // Write the pending indent. A more concise way of doing this would be: // // write!(self.out, "{: >n$}", "", n = self.pending_indentation as usize)?; @@ -436,9 +433,11 @@ impl Printer { // But that is significantly slower. This code is sufficiently hot, and indents can get // sufficiently large, that the difference is significant on some workloads. self.out.reserve(self.pending_indentation as usize); - self.out.extend(std::iter::repeat(' ').take(self.pending_indentation as usize)); + self.out.extend(iter::repeat(' ').take(self.pending_indentation as usize)); self.pending_indentation = 0; + self.out.push_str(string); + self.space -= string.len() as isize; } // Convenience functions to talk to the printer. From 9e794d7de3adcf4e91b04e1a05d4f84c86b54f66 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:51:07 -0800 Subject: [PATCH 57/69] Eliminate offset number from Fits frames PrintStackElems with pbreak=PrintStackBreak::Fits always carried a meaningless value offset=0. We can combine the two types PrintStackElem + PrintStackBreak into one PrintFrame enum that stores offset only for Broken frames. --- compiler/rustc_ast_pretty/src/pp.rs | 47 ++++++++++++----------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index 11114b5322077..de57820d2613d 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -176,15 +176,9 @@ impl Token { } #[derive(Copy, Clone)] -enum PrintStackBreak { +enum PrintFrame { Fits, - Broken(Breaks), -} - -#[derive(Copy, Clone)] -struct PrintStackElem { - offset: isize, - pbreak: PrintStackBreak, + Broken { offset: isize, breaks: Breaks }, } const SIZE_INFINITY: isize = 0xffff; @@ -209,7 +203,7 @@ pub struct Printer { /// advancing. scan_stack: VecDeque, /// Stack of blocks-in-progress being flushed by print - print_stack: Vec, + print_stack: Vec, /// Buffered indentation to avoid writing trailing whitespace pending_indentation: isize, /// The token most recently popped from the left boundary of the @@ -380,21 +374,19 @@ impl Printer { self.pending_indentation += amount; } - fn get_top(&self) -> PrintStackElem { - *self.print_stack.last().unwrap_or({ - &PrintStackElem { offset: 0, pbreak: PrintStackBreak::Broken(Breaks::Inconsistent) } - }) + fn get_top(&self) -> PrintFrame { + *self + .print_stack + .last() + .unwrap_or(&PrintFrame::Broken { offset: 0, breaks: Breaks::Inconsistent }) } fn print_begin(&mut self, token: BeginToken, size: isize) { if size > self.space { let col = self.margin - self.space + token.offset; - self.print_stack.push(PrintStackElem { - offset: col, - pbreak: PrintStackBreak::Broken(token.breaks), - }); + self.print_stack.push(PrintFrame::Broken { offset: col, breaks: token.breaks }); } else { - self.print_stack.push(PrintStackElem { offset: 0, pbreak: PrintStackBreak::Fits }); + self.print_stack.push(PrintFrame::Fits); } } @@ -403,20 +395,19 @@ impl Printer { } fn print_break(&mut self, token: BreakToken, size: isize) { - let top = self.get_top(); - match top.pbreak { - PrintStackBreak::Fits => { - self.space -= token.blank_space; + match self.get_top() { + PrintFrame::Fits => { self.indent(token.blank_space); + self.space -= token.blank_space; } - PrintStackBreak::Broken(Breaks::Consistent) => { - self.print_newline(top.offset + token.offset); - self.space = self.margin - (top.offset + token.offset); + PrintFrame::Broken { offset, breaks: Breaks::Consistent } => { + self.print_newline(offset + token.offset); + self.space = self.margin - (offset + token.offset); } - PrintStackBreak::Broken(Breaks::Inconsistent) => { + PrintFrame::Broken { offset, breaks: Breaks::Inconsistent } => { if size > self.space { - self.print_newline(top.offset + token.offset); - self.space = self.margin - (top.offset + token.offset); + self.print_newline(offset + token.offset); + self.space = self.margin - (offset + token.offset); } else { self.indent(token.blank_space); self.space -= token.blank_space; From 224536f4fee75a9825e7da0a8a332786cc1f52f8 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:55:24 -0800 Subject: [PATCH 58/69] Inline indent function --- compiler/rustc_ast_pretty/src/pp.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index de57820d2613d..726c114b10b1f 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -366,12 +366,7 @@ impl Printer { fn print_newline(&mut self, amount: isize) { self.out.push('\n'); - self.pending_indentation = 0; - self.indent(amount); - } - - fn indent(&mut self, amount: isize) { - self.pending_indentation += amount; + self.pending_indentation = amount; } fn get_top(&self) -> PrintFrame { @@ -397,7 +392,7 @@ impl Printer { fn print_break(&mut self, token: BreakToken, size: isize) { match self.get_top() { PrintFrame::Fits => { - self.indent(token.blank_space); + self.pending_indentation += token.blank_space; self.space -= token.blank_space; } PrintFrame::Broken { offset, breaks: Breaks::Consistent } => { @@ -409,7 +404,7 @@ impl Printer { self.print_newline(offset + token.offset); self.space = self.margin - (offset + token.offset); } else { - self.indent(token.blank_space); + self.pending_indentation += token.blank_space; self.space -= token.blank_space; } } From 51eeb82d9db71bfb90ed6e0149d032787bcb3ee6 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:56:12 -0800 Subject: [PATCH 59/69] Inline print_newline function --- compiler/rustc_ast_pretty/src/pp.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index 726c114b10b1f..9fc9282ac8056 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -364,11 +364,6 @@ impl Printer { } } - fn print_newline(&mut self, amount: isize) { - self.out.push('\n'); - self.pending_indentation = amount; - } - fn get_top(&self) -> PrintFrame { *self .print_stack @@ -396,12 +391,14 @@ impl Printer { self.space -= token.blank_space; } PrintFrame::Broken { offset, breaks: Breaks::Consistent } => { - self.print_newline(offset + token.offset); + self.out.push('\n'); + self.pending_indentation = offset + token.offset; self.space = self.margin - (offset + token.offset); } PrintFrame::Broken { offset, breaks: Breaks::Inconsistent } => { if size > self.space { - self.print_newline(offset + token.offset); + self.out.push('\n'); + self.pending_indentation = offset + token.offset; self.space = self.margin - (offset + token.offset); } else { self.pending_indentation += token.blank_space; From 21c1571e79e7f25f1a683befee33758c91693b6a Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 18:58:33 -0800 Subject: [PATCH 60/69] Deduplicate branches of print_break implementation --- compiler/rustc_ast_pretty/src/pp.rs | 33 ++++++++++++----------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index 9fc9282ac8056..82c40868d18f5 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -385,26 +385,21 @@ impl Printer { } fn print_break(&mut self, token: BreakToken, size: isize) { - match self.get_top() { - PrintFrame::Fits => { - self.pending_indentation += token.blank_space; - self.space -= token.blank_space; - } - PrintFrame::Broken { offset, breaks: Breaks::Consistent } => { - self.out.push('\n'); - self.pending_indentation = offset + token.offset; - self.space = self.margin - (offset + token.offset); - } - PrintFrame::Broken { offset, breaks: Breaks::Inconsistent } => { - if size > self.space { - self.out.push('\n'); - self.pending_indentation = offset + token.offset; - self.space = self.margin - (offset + token.offset); - } else { - self.pending_indentation += token.blank_space; - self.space -= token.blank_space; + let break_offset = + match self.get_top() { + PrintFrame::Fits => None, + PrintFrame::Broken { offset, breaks: Breaks::Consistent } => Some(offset), + PrintFrame::Broken { offset, breaks: Breaks::Inconsistent } => { + if size > self.space { Some(offset) } else { None } } - } + }; + if let Some(offset) = break_offset { + self.out.push('\n'); + self.pending_indentation = offset + token.offset; + self.space = self.margin - (offset + token.offset); + } else { + self.pending_indentation += token.blank_space; + self.space -= token.blank_space; } } From dcb0721a29f63cd2fb3a30487c81cfb657f0a7ee Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 19 Jan 2022 20:12:31 -0800 Subject: [PATCH 61/69] Support --bless for pp-exact pretty printer tests --- src/tools/compiletest/src/runtest.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index f039ba59d231c..2278501308539 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -500,7 +500,19 @@ impl<'test> TestCx<'test> { expected = expected.replace(&cr, ""); } - self.compare_source(&expected, &actual); + if !self.config.bless { + self.compare_source(&expected, &actual); + } else if expected != actual { + let filepath_buf; + let filepath = match &self.props.pp_exact { + Some(file) => { + filepath_buf = self.testpaths.file.parent().unwrap().join(file); + &filepath_buf + } + None => &self.testpaths.file, + }; + fs::write(filepath, &actual).unwrap(); + } // If we're only making sure that the output matches then just stop here if self.props.pretty_compare_only { From 4e17170c54c3e8bbff9cee1937dd6ab3b2001857 Mon Sep 17 00:00:00 2001 From: Artem Kryvokrysenko Date: Wed, 19 Jan 2022 00:59:44 -0800 Subject: [PATCH 62/69] rustdoc: auto create output directory when "--output-format json" This PR allows rustdoc to automatically create output directory in case it does not exist (when run with `--output-format json`). This fixes rustdoc crash: ```` $ rustdoc --output-format json -Z unstable-options src/main.rs error: couldn't generate documentation: No such file or directory (os error 2) | = note: failed to create or modify "doc/main.json" error: aborting due to previous error ```` With this fix behavior of `rustdoc --output-format json` becomes consistent with `rustdoc --output-format html` (which already auto-creates output directory if it's missing) --- src/librustdoc/json/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 81fbfd9fdbd16..f9e9fe0d3cf20 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -7,7 +7,7 @@ mod conversions; use std::cell::RefCell; -use std::fs::File; +use std::fs::{create_dir_all, File}; use std::path::PathBuf; use std::rc::Rc; @@ -18,13 +18,14 @@ use rustc_session::Session; use rustdoc_json_types as types; -use crate::clean; use crate::clean::types::{ExternalCrate, ExternalLocation}; use crate::config::RenderOptions; +use crate::docfs::PathError; use crate::error::Error; use crate::formats::cache::Cache; use crate::formats::FormatRenderer; use crate::json::conversions::{from_item_id, IntoWithTcx}; +use crate::{clean, try_err}; #[derive(Clone)] crate struct JsonRenderer<'tcx> { @@ -256,10 +257,13 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { .collect(), format_version: types::FORMAT_VERSION, }; - let mut p = self.out_path.clone(); + let out_dir = self.out_path.clone(); + try_err!(create_dir_all(&out_dir), out_dir); + + let mut p = out_dir; p.push(output.index.get(&output.root).unwrap().name.clone().unwrap()); p.set_extension("json"); - let file = File::create(&p).map_err(|error| Error { error: error.to_string(), file: p })?; + let file = try_err!(File::create(&p), p); serde_json::ser::to_writer(&file, &output).unwrap(); Ok(()) } From c29b637875e837f63d726901cd727199b08a5264 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 20 Jan 2022 14:47:31 +0100 Subject: [PATCH 63/69] update comments --- compiler/rustc_const_eval/src/interpret/util.rs | 6 +++++- compiler/rustc_typeck/src/check/writeback.rs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/util.rs b/compiler/rustc_const_eval/src/interpret/util.rs index 3dde34a64103e..6a3378a3896e3 100644 --- a/compiler/rustc_const_eval/src/interpret/util.rs +++ b/compiler/rustc_const_eval/src/interpret/util.rs @@ -3,7 +3,11 @@ use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeVisitor}; use std::convert::TryInto; use std::ops::ControlFlow; -/// Returns `true` if a used generic parameter requires substitution. +/// Checks whether a type contains generic parameters which require substitution. +/// +/// In case it does, returns a `TooGeneric` const eval error. Note that due to polymorphization +/// types may be "concrete enough" even though they still contain generic parameters in +/// case these parameters are unused. crate fn ensure_monomorphic_enough<'tcx, T>(tcx: TyCtxt<'tcx>, ty: T) -> InterpResult<'tcx> where T: TypeFoldable<'tcx>, diff --git a/compiler/rustc_typeck/src/check/writeback.rs b/compiler/rustc_typeck/src/check/writeback.rs index f50f3c39c8882..ec88bdf4a370f 100644 --- a/compiler/rustc_typeck/src/check/writeback.rs +++ b/compiler/rustc_typeck/src/check/writeback.rs @@ -43,7 +43,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let item_def_id = self.tcx.hir().local_def_id(item_id); // This attribute causes us to dump some writeback information - // in the form of errors, which is uSymbol for unit tests. + // in the form of errors, which is used for unit tests. let rustc_dump_user_substs = self.tcx.has_attr(item_def_id.to_def_id(), sym::rustc_dump_user_substs); From 855c17643ab0ae64d38c336f14b122689efdc8d4 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Thu, 20 Jan 2022 21:18:04 +0100 Subject: [PATCH 64/69] add script to prevent point releases with same number as existing ones --- .github/workflows/ci.yml | 9 ++++++ src/ci/github-actions/ci.yml | 4 +++ .../scripts/verify-stable-version-number.sh | 30 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100755 src/ci/scripts/verify-stable-version-number.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe5dedb6ba4b7..1f872569ab144 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,6 +128,9 @@ jobs: - name: ensure backported commits are in upstream branches run: src/ci/scripts/verify-backported-commits.sh if: success() && !env.SKIP_JOB + - name: ensure the stable version number is correct + run: src/ci/scripts/verify-stable-version-number.sh + if: success() && !env.SKIP_JOB - name: run the build run: src/ci/scripts/run-build-from-ci.sh env: @@ -502,6 +505,9 @@ jobs: - name: ensure backported commits are in upstream branches run: src/ci/scripts/verify-backported-commits.sh if: success() && !env.SKIP_JOB + - name: ensure the stable version number is correct + run: src/ci/scripts/verify-stable-version-number.sh + if: success() && !env.SKIP_JOB - name: run the build run: src/ci/scripts/run-build-from-ci.sh env: @@ -612,6 +618,9 @@ jobs: - name: ensure backported commits are in upstream branches run: src/ci/scripts/verify-backported-commits.sh if: success() && !env.SKIP_JOB + - name: ensure the stable version number is correct + run: src/ci/scripts/verify-stable-version-number.sh + if: success() && !env.SKIP_JOB - name: run the build run: src/ci/scripts/run-build-from-ci.sh env: diff --git a/src/ci/github-actions/ci.yml b/src/ci/github-actions/ci.yml index a70cdc4b519e6..cc282fc2f0e44 100644 --- a/src/ci/github-actions/ci.yml +++ b/src/ci/github-actions/ci.yml @@ -206,6 +206,10 @@ x--expand-yaml-anchors--remove: run: src/ci/scripts/verify-backported-commits.sh <<: *step + - name: ensure the stable version number is correct + run: src/ci/scripts/verify-stable-version-number.sh + <<: *step + - name: run the build run: src/ci/scripts/run-build-from-ci.sh env: diff --git a/src/ci/scripts/verify-stable-version-number.sh b/src/ci/scripts/verify-stable-version-number.sh new file mode 100755 index 0000000000000..82eb3833ccf98 --- /dev/null +++ b/src/ci/scripts/verify-stable-version-number.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# On the stable channel, check whether we're trying to build artifacts with the +# same version number of a release that's already been published, and fail the +# build if that's the case. +# +# It's a mistake whenever that happens: the release process won't start if it +# detects a duplicate version number, and the artifacts would have to be +# rebuilt anyway. + +set -euo pipefail +IFS=$'\n\t' + +if [[ "$(cat src/ci/channel)" != "stable" ]]; then + echo "This script only works on the stable channel. Skipping the check." + exit 0 +fi + +version="$(cat src/version)" +url="https://static.rust-lang.org/dist/channel-rust-${version}.toml" + +if curl --silent --fail "${url}" >/dev/null; then + echo "The version number ${version} matches an existing release." + echo + echo "If you're trying to prepare a point release, remember to change the" + echo "version number in the src/version file." + exit 1 +else + echo "The version number ${version} does not match any released version!" + exit 0 +fi From 682ef4db8060a0b26266cdcf5631c5776cd45e0a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 13 Jan 2022 15:50:11 +0100 Subject: [PATCH 65/69] Exclude "test" from doc_auto_cfg rendering --- src/librustdoc/clean/cfg.rs | 61 ++++++++++++++++++++++++++--------- src/librustdoc/clean/types.rs | 5 ++- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index dfee2b702c1c8..2c1dcad1afc6d 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -43,23 +43,22 @@ crate struct InvalidCfgError { impl Cfg { /// Parses a `NestedMetaItem` into a `Cfg`. - fn parse_nested(nested_cfg: &NestedMetaItem) -> Result { + fn parse_nested( + nested_cfg: &NestedMetaItem, + exclude: &[Symbol], + ) -> Result, InvalidCfgError> { match nested_cfg { - NestedMetaItem::MetaItem(ref cfg) => Cfg::parse(cfg), + NestedMetaItem::MetaItem(ref cfg) => Cfg::parse_without(cfg, exclude), NestedMetaItem::Literal(ref lit) => { Err(InvalidCfgError { msg: "unexpected literal", span: lit.span }) } } } - /// Parses a `MetaItem` into a `Cfg`. - /// - /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or - /// `target_os = "redox"`. - /// - /// If the content is not properly formatted, it will return an error indicating what and where - /// the error is. - crate fn parse(cfg: &MetaItem) -> Result { + crate fn parse_without( + cfg: &MetaItem, + exclude: &[Symbol], + ) -> Result, InvalidCfgError> { let name = match cfg.ident() { Some(ident) => ident.name, None => { @@ -70,9 +69,21 @@ impl Cfg { } }; match cfg.kind { - MetaItemKind::Word => Ok(Cfg::Cfg(name, None)), + MetaItemKind::Word => { + if exclude.contains(&name) { + Ok(None) + } else { + Ok(Some(Cfg::Cfg(name, None))) + } + } MetaItemKind::NameValue(ref lit) => match lit.kind { - LitKind::Str(value, _) => Ok(Cfg::Cfg(name, Some(value))), + LitKind::Str(value, _) => { + if exclude.contains(&name) { + Ok(None) + } else { + Ok(Some(Cfg::Cfg(name, Some(value)))) + } + } _ => Err(InvalidCfgError { // FIXME: if the main #[cfg] syntax decided to support non-string literals, // this should be changed as well. @@ -81,23 +92,43 @@ impl Cfg { }), }, MetaItemKind::List(ref items) => { - let mut sub_cfgs = items.iter().map(Cfg::parse_nested); - match name { + let sub_cfgs = items.iter().filter_map(|i| match Cfg::parse_nested(i, exclude) { + Ok(Some(c)) => Some(Ok(c)), + Err(e) => Some(Err(e)), + _ => None, + }); + let ret = match name { sym::all => sub_cfgs.fold(Ok(Cfg::True), |x, y| Ok(x? & y?)), sym::any => sub_cfgs.fold(Ok(Cfg::False), |x, y| Ok(x? | y?)), sym::not => { + let mut sub_cfgs = sub_cfgs.collect::>(); if sub_cfgs.len() == 1 { - Ok(!sub_cfgs.next().unwrap()?) + Ok(!sub_cfgs.pop().unwrap()?) } else { Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span }) } } _ => Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }), + }; + match ret { + Ok(c) => Ok(Some(c)), + Err(e) => Err(e), } } } } + /// Parses a `MetaItem` into a `Cfg`. + /// + /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or + /// `target_os = "redox"`. + /// + /// If the content is not properly formatted, it will return an error indicating what and where + /// the error is. + crate fn parse(cfg: &MetaItem) -> Result { + Self::parse_without(cfg, &[]).map(|ret| ret.unwrap()) + } + /// Checks whether the given configuration can be matched in the current session. /// /// Equivalent to `attr::cfg_matches`. diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index fac1a0817e033..347f9d0a47cb7 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -831,7 +831,10 @@ impl AttributesExt for [ast::Attribute] { self.iter() .filter(|attr| attr.has_name(sym::cfg)) .filter_map(|attr| single(attr.meta_item_list()?)) - .filter_map(|attr| Cfg::parse(attr.meta_item()?).ok()) + .filter_map(|attr| match Cfg::parse_without(attr.meta_item()?, &[sym::test]) { + Ok(Some(c)) => Some(c), + _ => None, + }) .filter(|cfg| !hidden_cfg.contains(cfg)) .fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg) } else { From fd005f53c204136732c446dc476afc1266de730b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 13 Jan 2022 15:50:21 +0100 Subject: [PATCH 66/69] Update doc_auto_cfg test --- src/test/rustdoc/doc-auto-cfg.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/rustdoc/doc-auto-cfg.rs b/src/test/rustdoc/doc-auto-cfg.rs index fcdd83545696b..57dd0529535f6 100644 --- a/src/test/rustdoc/doc-auto-cfg.rs +++ b/src/test/rustdoc/doc-auto-cfg.rs @@ -3,6 +3,12 @@ #![crate_name = "foo"] // @has foo/fn.foo.html -// @has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-test' -#[cfg(not(test))] +// @has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-doctest' +#[cfg(not(doctest))] pub fn foo() {} + +// @has foo/fn.bar.html +// @has - '//*[@class="item-info"]/*[@class="stab portability"]' 'doc' +// @!has - '//*[@class="item-info"]/*[@class="stab portability"]' 'test' +#[cfg(any(test, doc))] +pub fn bar() {} From caec4a23f201ff4e2bbfc3f8fd8b97e5e64fc20d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 16 Jan 2022 21:03:16 +0100 Subject: [PATCH 67/69] Extra cfg_hide a bit to handle inner cfgs --- src/librustdoc/clean/cfg.rs | 21 ++++++++------------- src/librustdoc/clean/types.rs | 3 +-- src/librustdoc/visit_ast.rs | 1 + src/test/rustdoc/doc-cfg-hide.rs | 2 +- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 2c1dcad1afc6d..afa02f1e5c73e 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -8,6 +8,7 @@ use std::mem; use std::ops; use rustc_ast::{LitKind, MetaItem, MetaItemKind, NestedMetaItem}; +use rustc_data_structures::fx::FxHashSet; use rustc_feature::Features; use rustc_session::parse::ParseSess; use rustc_span::symbol::{sym, Symbol}; @@ -45,7 +46,7 @@ impl Cfg { /// Parses a `NestedMetaItem` into a `Cfg`. fn parse_nested( nested_cfg: &NestedMetaItem, - exclude: &[Symbol], + exclude: &FxHashSet, ) -> Result, InvalidCfgError> { match nested_cfg { NestedMetaItem::MetaItem(ref cfg) => Cfg::parse_without(cfg, exclude), @@ -57,7 +58,7 @@ impl Cfg { crate fn parse_without( cfg: &MetaItem, - exclude: &[Symbol], + exclude: &FxHashSet, ) -> Result, InvalidCfgError> { let name = match cfg.ident() { Some(ident) => ident.name, @@ -70,19 +71,13 @@ impl Cfg { }; match cfg.kind { MetaItemKind::Word => { - if exclude.contains(&name) { - Ok(None) - } else { - Ok(Some(Cfg::Cfg(name, None))) - } + let cfg = Cfg::Cfg(name, None); + if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) } } MetaItemKind::NameValue(ref lit) => match lit.kind { LitKind::Str(value, _) => { - if exclude.contains(&name) { - Ok(None) - } else { - Ok(Some(Cfg::Cfg(name, Some(value)))) - } + let cfg = Cfg::Cfg(name, Some(value)); + if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) } } _ => Err(InvalidCfgError { // FIXME: if the main #[cfg] syntax decided to support non-string literals, @@ -126,7 +121,7 @@ impl Cfg { /// If the content is not properly formatted, it will return an error indicating what and where /// the error is. crate fn parse(cfg: &MetaItem) -> Result { - Self::parse_without(cfg, &[]).map(|ret| ret.unwrap()) + Self::parse_without(cfg, &FxHashSet::default()).map(|ret| ret.unwrap()) } /// Checks whether the given configuration can be matched in the current session. diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 347f9d0a47cb7..92e8b89311577 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -831,11 +831,10 @@ impl AttributesExt for [ast::Attribute] { self.iter() .filter(|attr| attr.has_name(sym::cfg)) .filter_map(|attr| single(attr.meta_item_list()?)) - .filter_map(|attr| match Cfg::parse_without(attr.meta_item()?, &[sym::test]) { + .filter_map(|attr| match Cfg::parse_without(attr.meta_item()?, hidden_cfg) { Ok(Some(c)) => Some(c), _ => None, }) - .filter(|cfg| !hidden_cfg.contains(cfg)) .fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg) } else { Cfg::True diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 90cb5d586c211..2cbb3324a5e04 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -141,6 +141,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { }) .collect::>() }) + .chain([Cfg::Cfg(sym::test, None)].into_iter()) .collect(); self.cx.cache.exact_paths = self.exact_paths; diff --git a/src/test/rustdoc/doc-cfg-hide.rs b/src/test/rustdoc/doc-cfg-hide.rs index 424fa6d6a911f..636957fe9980d 100644 --- a/src/test/rustdoc/doc-cfg-hide.rs +++ b/src/test/rustdoc/doc-cfg-hide.rs @@ -26,7 +26,7 @@ pub struct Hyperdulia; // @has 'oud/struct.Oystercatcher.html' // @count - '//*[@class="stab portability"]' 1 -// @matches - '//*[@class="stab portability"]' 'crate features solecism and oystercatcher' +// @matches - '//*[@class="stab portability"]' 'crate feature oystercatcher only' // compile-flags:--cfg feature="oystercatcher" #[cfg(all(feature = "solecism", feature = "oystercatcher"))] pub struct Oystercatcher; From b0df7653d0cc7256e9bb34a0bc3c7e00f3afaea7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 20 Jan 2022 22:13:32 +0100 Subject: [PATCH 68/69] More clean up --- src/librustdoc/clean/cfg.rs | 7 ++----- src/librustdoc/clean/types.rs | 5 ++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index afa02f1e5c73e..b72d262417755 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -87,11 +87,8 @@ impl Cfg { }), }, MetaItemKind::List(ref items) => { - let sub_cfgs = items.iter().filter_map(|i| match Cfg::parse_nested(i, exclude) { - Ok(Some(c)) => Some(Ok(c)), - Err(e) => Some(Err(e)), - _ => None, - }); + let sub_cfgs = + items.iter().filter_map(|i| Cfg::parse_nested(i, exclude).transpose()); let ret = match name { sym::all => sub_cfgs.fold(Ok(Cfg::True), |x, y| Ok(x? & y?)), sym::any => sub_cfgs.fold(Ok(Cfg::False), |x, y| Ok(x? | y?)), diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 92e8b89311577..7ae7b940f26b8 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -831,9 +831,8 @@ impl AttributesExt for [ast::Attribute] { self.iter() .filter(|attr| attr.has_name(sym::cfg)) .filter_map(|attr| single(attr.meta_item_list()?)) - .filter_map(|attr| match Cfg::parse_without(attr.meta_item()?, hidden_cfg) { - Ok(Some(c)) => Some(c), - _ => None, + .filter_map(|attr| { + Cfg::parse_without(attr.meta_item()?, hidden_cfg).ok().flatten() }) .fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg) } else { From 028a3e77f98e91fb8d9c8b0055a0317c011b1f8c Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Thu, 20 Jan 2022 22:59:54 +0100 Subject: [PATCH 69/69] backport the 1.58.1 relnotes to master --- RELEASES.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 460c78b14d138..f44291c1fa302 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,18 @@ +Version 1.58.1 (2022-01-19) +=========================== + +* Fix race condition in `std::fs::remove_dir_all` ([CVE-2022-21658]) +* [Handle captured arguments in the `useless_format` Clippy lint][clippy/8295] +* [Move `non_send_fields_in_send_ty` Clippy lint to nursery][clippy/8075] +* [Fix wrong error message displayed when some imports are missing][91254] +* [Fix rustfmt not formatting generated files from stdin][92912] + +[CVE-2022-21658]: https://www.cve.org/CVERecord?id=CVE-2022-21658] +[91254]: https://github.com/rust-lang/rust/pull/91254 +[92912]: https://github.com/rust-lang/rust/pull/92912 +[clippy/8075]: https://github.com/rust-lang/rust-clippy/pull/8075 +[clippy/8295]: https://github.com/rust-lang/rust-clippy/pull/8295 + Version 1.58.0 (2022-01-13) ==========================