From cc9f16bf71c460c57521b97907537dd6da81370a Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 21 Jan 2022 09:57:05 -0800 Subject: [PATCH 1/7] Reduce regression test --- src/test/ui/generator/issue-93161.rs | 72 +--------------------------- 1 file changed, 2 insertions(+), 70 deletions(-) diff --git a/src/test/ui/generator/issue-93161.rs b/src/test/ui/generator/issue-93161.rs index 9988acbcb73e1..cc56d2be896d8 100644 --- a/src/test/ui/generator/issue-93161.rs +++ b/src/test/ui/generator/issue-93161.rs @@ -1,31 +1,8 @@ // edition:2021 -// run-pass +// build-pass #![feature(never_type)] -use std::future::Future; - -// See if we can run a basic `async fn` -pub async fn foo(x: &u32, y: u32) -> u32 { - let y = &y; - let z = 9; - let z = &z; - let y = async { *y + *z }.await; - let a = 10; - let a = &a; - *x + y + *a -} - -async fn add(x: u32, y: u32) -> u32 { - let a = async { x + y }; - a.await -} - -async fn build_aggregate(a: u32, b: u32, c: u32, d: u32) -> u32 { - let x = (add(a, b).await, add(c, d).await); - x.0 + x.1 -} - enum Never {} fn never() -> Never { panic!() @@ -43,51 +20,6 @@ async fn includes_never(crash: bool, x: u32) -> u32 { result } -async fn partial_init(x: u32) -> u32 { - #[allow(unreachable_code)] - let _x: (String, !) = (String::new(), return async { x + x }.await); -} - -async fn read_exact(_from: &mut &[u8], _to: &mut [u8]) -> Option<()> { - Some(()) -} - -async fn hello_world() { - let data = [0u8; 1]; - let mut reader = &data[..]; - - let mut marker = [0u8; 1]; - read_exact(&mut reader, &mut marker).await.unwrap(); -} - -fn run_fut(fut: impl Future) -> T { - use std::sync::Arc; - use std::task::{Context, Poll, Wake, Waker}; - - struct MyWaker; - impl Wake for MyWaker { - fn wake(self: Arc) { - unimplemented!() - } - } - - let waker = Waker::from(Arc::new(MyWaker)); - let mut context = Context::from_waker(&waker); - - let mut pinned = Box::pin(fut); - loop { - match pinned.as_mut().poll(&mut context) { - Poll::Pending => continue, - Poll::Ready(v) => return v, - } - } -} - fn main() { - let x = 5; - assert_eq!(run_fut(foo(&x, 7)), 31); - assert_eq!(run_fut(build_aggregate(1, 2, 3, 4)), 10); - assert_eq!(run_fut(includes_never(false, 4)), 16); - assert_eq!(run_fut(partial_init(4)), 8); - run_fut(hello_world()); + let _ = includes_never(false, 4); } From f77a40773f2c0ddfbff81b93ceae7fa83bf27922 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 21 Jan 2022 15:32:22 -0800 Subject: [PATCH 2/7] Allow uninhabited types in generator interiors Drop tracking in the generator interior type checking pass would count all values in unreachable code as dropped (e.g. code after a call to a function with an uninhabited return type), which would lead to those values not being included in the witness type. This resulted in the type checker being more precise than the corresponding sanity check in the MIR transform. This patch changes the check in the MIR code to match the results of typeck by skipping uninhabited types. --- compiler/rustc_mir_transform/src/generator.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_mir_transform/src/generator.rs b/compiler/rustc_mir_transform/src/generator.rs index 433a1c6ad67cc..72faea10a95b8 100644 --- a/compiler/rustc_mir_transform/src/generator.rs +++ b/compiler/rustc_mir_transform/src/generator.rs @@ -749,9 +749,15 @@ fn sanitize_witness<'tcx>( } let decl_ty = tcx.normalize_erasing_regions(param_env, decl.ty); + let is_uninhabited = tcx.is_ty_uninhabited_from( + tcx.parent_module(tcx.hir().local_def_id_to_hir_id(did.expect_local())).to_def_id(), + decl_ty, + param_env, + ); + // Sanity check that typeck knows about the type of locals which are // live across a suspension point - if !allowed.contains(&decl_ty) && !allowed_upvars.contains(&decl_ty) { + if !is_uninhabited && !allowed.contains(&decl_ty) && !allowed_upvars.contains(&decl_ty) { span_bug!( body.span, "Broken MIR: generator contains type {} in MIR, \ From 06d5a962fbd1cb89b69d220dcd88891f887953dc Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 25 Jan 2022 16:13:07 -0800 Subject: [PATCH 3/7] Support break and continue with block targets Issue #93197 --- .../drop_ranges/cfg_build.rs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 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 d7d52ab823cee..5be8343969c7e 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 @@ -209,6 +209,45 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { self.drop_ranges.add_control_edge(self.expr_index + 1, self.expr_index + 1); } } + + /// Break and continue expression targets might be another expression or a block, + /// but this analysis only looks at expressions. In case a break has a block as a + /// target, this will find the last expression in the block and return its HirId + /// instead. + fn find_target_expression(&self, hir_id: HirId) -> HirId { + let node = self.hir.get(hir_id); + match node { + hir::Node::Expr(_) => hir_id, + hir::Node::Block(b) => b.expr.map_or_else( + // If there is no tail expression, there will be at least one statement in the + // block because the block contains a break or continue statement. + || b.stmts.last().unwrap().hir_id, + |expr| expr.hir_id, + ), + hir::Node::Param(..) + | hir::Node::Item(..) + | hir::Node::ForeignItem(..) + | hir::Node::TraitItem(..) + | hir::Node::ImplItem(..) + | hir::Node::Variant(..) + | hir::Node::Field(..) + | hir::Node::AnonConst(..) + | hir::Node::Stmt(..) + | hir::Node::PathSegment(..) + | hir::Node::Ty(..) + | hir::Node::TraitRef(..) + | hir::Node::Binding(..) + | hir::Node::Pat(..) + | hir::Node::Arm(..) + | hir::Node::Local(..) + | hir::Node::Ctor(..) + | hir::Node::Lifetime(..) + | hir::Node::GenericParam(..) + | hir::Node::Visibility(..) + | hir::Node::Crate(..) + | hir::Node::Infer(..) => bug!("Unsupported branch target: {:?}", node), + } + } } impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { @@ -334,7 +373,8 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { } 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); + self.drop_ranges + .add_control_edge_hir_id(self.expr_index, self.find_target_expression(target)); } ExprKind::Call(f, args) => { @@ -462,11 +502,13 @@ impl DropRangesBuilder { /// 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) { + trace!("processing deferred edges. post_order_map={:#?}", self.post_order_map); 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); + let to = *self.post_order_map.get(&to).expect("Expression ID not found"); + trace!("target edge PostOrderId={:?}", to); self.add_control_edge(from, to) }); } From 0c7f1f9f07a6b81f9d753ce4673612788fb9de8d Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 25 Jan 2022 16:19:06 -0800 Subject: [PATCH 4/7] Apply code review comments --- compiler/rustc_mir_transform/src/generator.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_mir_transform/src/generator.rs b/compiler/rustc_mir_transform/src/generator.rs index 72faea10a95b8..be68d5a8dd603 100644 --- a/compiler/rustc_mir_transform/src/generator.rs +++ b/compiler/rustc_mir_transform/src/generator.rs @@ -749,7 +749,7 @@ fn sanitize_witness<'tcx>( } let decl_ty = tcx.normalize_erasing_regions(param_env, decl.ty); - let is_uninhabited = tcx.is_ty_uninhabited_from( + let is_inhabited = !tcx.is_ty_uninhabited_from( tcx.parent_module(tcx.hir().local_def_id_to_hir_id(did.expect_local())).to_def_id(), decl_ty, param_env, @@ -757,7 +757,12 @@ fn sanitize_witness<'tcx>( // Sanity check that typeck knows about the type of locals which are // live across a suspension point - if !is_uninhabited && !allowed.contains(&decl_ty) && !allowed_upvars.contains(&decl_ty) { + if is_inhabited || allowed.contains(&decl_ty) || allowed_upvars.contains(&decl_ty) { + // This type which appears in the generator either... + // - is uninhabited, in which case it can't actually be captured at runtime + // - appears in the approximation from the static type (`allowed`) + // - appears in the list of upvars ... + } else { span_bug!( body.span, "Broken MIR: generator contains type {} in MIR, \ From 44ca6bc66039429722c8ab378dc9ef816edb18e9 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 25 Jan 2022 16:28:07 -0800 Subject: [PATCH 5/7] Apply code review suggestion better --- compiler/rustc_mir_transform/src/generator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_mir_transform/src/generator.rs b/compiler/rustc_mir_transform/src/generator.rs index be68d5a8dd603..1c1f6c831b091 100644 --- a/compiler/rustc_mir_transform/src/generator.rs +++ b/compiler/rustc_mir_transform/src/generator.rs @@ -749,7 +749,7 @@ fn sanitize_witness<'tcx>( } let decl_ty = tcx.normalize_erasing_regions(param_env, decl.ty); - let is_inhabited = !tcx.is_ty_uninhabited_from( + let is_uninhabited = tcx.is_ty_uninhabited_from( tcx.parent_module(tcx.hir().local_def_id_to_hir_id(did.expect_local())).to_def_id(), decl_ty, param_env, @@ -757,7 +757,7 @@ fn sanitize_witness<'tcx>( // Sanity check that typeck knows about the type of locals which are // live across a suspension point - if is_inhabited || allowed.contains(&decl_ty) || allowed_upvars.contains(&decl_ty) { + if is_uninhabited || allowed.contains(&decl_ty) || allowed_upvars.contains(&decl_ty) { // This type which appears in the generator either... // - is uninhabited, in which case it can't actually be captured at runtime // - appears in the approximation from the static type (`allowed`) From f462bca6013f91ba277182234e4abe58c7537860 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 25 Jan 2022 16:28:21 -0800 Subject: [PATCH 6/7] Re-enable drop tracking --- compiler/rustc_typeck/src/check/generator_interior.rs | 2 +- src/test/ui/async-await/async-fn-nonsend.rs | 4 ---- src/test/ui/async-await/unresolved_type_param.rs | 4 ---- src/test/ui/generator/drop-control-flow.rs | 4 ---- src/test/ui/generator/issue-57478.rs | 4 ---- src/test/ui/generator/partial-drop.rs | 4 ---- 6 files changed, 1 insertion(+), 21 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index c6b92db88ae8a..600e55151e63a 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -25,7 +25,7 @@ mod drop_ranges; // FIXME(eholk): This flag is here to give a quick way to disable drop tracking in case we find // unexpected breakages while it's still new. It should be removed before too long. For example, // see #93161. -const ENABLE_DROP_TRACKING: bool = false; +const ENABLE_DROP_TRACKING: bool = true; struct InteriorVisitor<'a, 'tcx> { fcx: &'a FnCtxt<'a, 'tcx>, diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index a1a05c0acba47..c5453b67ef5b6 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -1,10 +1,6 @@ // edition:2018 // compile-flags: --crate-type lib -// FIXME(eholk): temporarily disabled while drop range tracking is disabled -// (see generator_interior.rs:27) -// ignore-test - use std::{cell::RefCell, fmt::Debug, rc::Rc}; fn non_sync() -> impl Debug { diff --git a/src/test/ui/async-await/unresolved_type_param.rs b/src/test/ui/async-await/unresolved_type_param.rs index 187356ca14021..d313691b38857 100644 --- a/src/test/ui/async-await/unresolved_type_param.rs +++ b/src/test/ui/async-await/unresolved_type_param.rs @@ -3,10 +3,6 @@ // (rather than give a general error message) // edition:2018 -// FIXME(eholk): temporarily disabled while drop range tracking is disabled -// (see generator_interior.rs:27) -// ignore-test - async fn bar() -> () {} async fn foo() { diff --git a/src/test/ui/generator/drop-control-flow.rs b/src/test/ui/generator/drop-control-flow.rs index 8540f7617acde..6319a29f5b7d0 100644 --- a/src/test/ui/generator/drop-control-flow.rs +++ b/src/test/ui/generator/drop-control-flow.rs @@ -1,9 +1,5 @@ // build-pass -// FIXME(eholk): temporarily disabled while drop range tracking is disabled -// (see generator_interior.rs:27) -// ignore-test - // 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. diff --git a/src/test/ui/generator/issue-57478.rs b/src/test/ui/generator/issue-57478.rs index 5c23ecbae3273..39710febdb95c 100644 --- a/src/test/ui/generator/issue-57478.rs +++ b/src/test/ui/generator/issue-57478.rs @@ -1,9 +1,5 @@ // check-pass -// FIXME(eholk): temporarily disabled while drop range tracking is disabled -// (see generator_interior.rs:27) -// ignore-test - #![feature(negative_impls, generators)] struct Foo; diff --git a/src/test/ui/generator/partial-drop.rs b/src/test/ui/generator/partial-drop.rs index e89e4b61bbff7..36f6e78cb3bfe 100644 --- a/src/test/ui/generator/partial-drop.rs +++ b/src/test/ui/generator/partial-drop.rs @@ -1,7 +1,3 @@ -// FIXME(eholk): temporarily disabled while drop range tracking is disabled -// (see generator_interior.rs:27) -// ignore-test - #![feature(negative_impls, generators)] struct Foo; From e5b2270c337ab8bfde3e45280534186bac3734ea Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 31 Jan 2022 14:10:25 -0800 Subject: [PATCH 7/7] Handle Continue correctly --- compiler/rustc_ast/src/ast.rs | 2 +- .../drop_ranges/cfg_build.rs | 135 ++++++++++++------ src/test/ui/generator/drop-control-flow.rs | 17 +++ 3 files changed, 107 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 7c19559ed91f5..e9135b7163025 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -54,7 +54,7 @@ mod tests; /// ``` /// /// `'outer` is a label. -#[derive(Clone, Encodable, Decodable, Copy, HashStable_Generic)] +#[derive(Clone, Encodable, Decodable, Copy, HashStable_Generic, Eq, PartialEq)] pub struct Label { pub ident: Ident, } 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 5be8343969c7e..e2a4d9c1b3af7 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 @@ -4,7 +4,7 @@ use super::{ }; use hir::{ intravisit::{self, Visitor}, - Body, Expr, ExprKind, Guard, HirId, + Body, Expr, ExprKind, Guard, HirId, LoopIdError, }; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; @@ -85,6 +85,7 @@ struct DropRangeVisitor<'a, 'tcx> { expr_index: PostOrderId, tcx: TyCtxt<'tcx>, typeck_results: &'a TypeckResults<'tcx>, + label_stack: Vec<(Option, PostOrderId)>, } impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { @@ -101,7 +102,15 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { hir, num_exprs, ); - Self { hir, places, drop_ranges, expr_index: PostOrderId::from_u32(0), typeck_results, tcx } + Self { + hir, + places, + drop_ranges, + expr_index: PostOrderId::from_u32(0), + typeck_results, + tcx, + label_stack: vec![], + } } fn record_drop(&mut self, value: TrackedValue) { @@ -210,46 +219,61 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { } } - /// Break and continue expression targets might be another expression or a block, - /// but this analysis only looks at expressions. In case a break has a block as a - /// target, this will find the last expression in the block and return its HirId - /// instead. - fn find_target_expression(&self, hir_id: HirId) -> HirId { - let node = self.hir.get(hir_id); - match node { - hir::Node::Expr(_) => hir_id, - hir::Node::Block(b) => b.expr.map_or_else( - // If there is no tail expression, there will be at least one statement in the - // block because the block contains a break or continue statement. - || b.stmts.last().unwrap().hir_id, - |expr| expr.hir_id, - ), - hir::Node::Param(..) - | hir::Node::Item(..) - | hir::Node::ForeignItem(..) - | hir::Node::TraitItem(..) - | hir::Node::ImplItem(..) - | hir::Node::Variant(..) - | hir::Node::Field(..) - | hir::Node::AnonConst(..) - | hir::Node::Stmt(..) - | hir::Node::PathSegment(..) - | hir::Node::Ty(..) - | hir::Node::TraitRef(..) - | hir::Node::Binding(..) - | hir::Node::Pat(..) - | hir::Node::Arm(..) - | hir::Node::Local(..) - | hir::Node::Ctor(..) - | hir::Node::Lifetime(..) - | hir::Node::GenericParam(..) - | hir::Node::Visibility(..) - | hir::Node::Crate(..) - | hir::Node::Infer(..) => bug!("Unsupported branch target: {:?}", node), - } + /// Map a Destination to an equivalent expression node + /// + /// The destination field of a Break or Continue expression can target either an + /// expression or a block. The drop range analysis, however, only deals in + /// expression nodes, so blocks that might be the destination of a Break or Continue + /// will not have a PostOrderId. + /// + /// If the destination is an expression, this function will simply return that expression's + /// hir_id. If the destination is a block, this function will return the hir_id of last + /// expression in the block. + fn find_target_expression_from_destination( + &self, + destination: hir::Destination, + ) -> Result { + destination.target_id.map(|target| { + let node = self.hir.get(target); + match node { + hir::Node::Expr(_) => target, + hir::Node::Block(b) => find_last_block_expression(b), + hir::Node::Param(..) + | hir::Node::Item(..) + | hir::Node::ForeignItem(..) + | hir::Node::TraitItem(..) + | hir::Node::ImplItem(..) + | hir::Node::Variant(..) + | hir::Node::Field(..) + | hir::Node::AnonConst(..) + | hir::Node::Stmt(..) + | hir::Node::PathSegment(..) + | hir::Node::Ty(..) + | hir::Node::TraitRef(..) + | hir::Node::Binding(..) + | hir::Node::Pat(..) + | hir::Node::Arm(..) + | hir::Node::Local(..) + | hir::Node::Ctor(..) + | hir::Node::Lifetime(..) + | hir::Node::GenericParam(..) + | hir::Node::Visibility(..) + | hir::Node::Crate(..) + | hir::Node::Infer(..) => bug!("Unsupported branch target: {:?}", node), + } + }) } } +fn find_last_block_expression(block: &hir::Block<'_>) -> HirId { + block.expr.map_or_else( + // If there is no tail expression, there will be at least one statement in the + // block because the block contains a break or continue statement. + || block.stmts.last().unwrap().hir_id, + |expr| expr.hir_id, + ) +} + impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut reinit = None; @@ -359,8 +383,9 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { }); } - ExprKind::Loop(body, ..) => { + ExprKind::Loop(body, label, ..) => { let loop_begin = self.expr_index + 1; + self.label_stack.push((label, 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 @@ -370,11 +395,31 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { self.visit_block(body); self.drop_ranges.add_control_edge(self.expr_index, loop_begin); } + self.label_stack.pop(); } - 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, self.find_target_expression(target)); + // Find the loop entry by searching through the label stack for either the last entry + // (if label is none), or the first entry where the label matches this one. The Loop + // case maintains this stack mapping labels to the PostOrderId for the loop entry. + ExprKind::Continue(hir::Destination { label, .. }, ..) => self + .label_stack + .iter() + .rev() + .find(|(loop_label, _)| label.is_none() || *loop_label == label) + .map_or((), |(_, target)| { + self.drop_ranges.add_control_edge(self.expr_index, *target) + }), + + ExprKind::Break(destination, ..) => { + // destination either points to an expression or to a block. We use + // find_target_expression_from_destination to use the last expression of the block + // if destination points to a block. + // + // We add an edge to the hir_id of the expression/block we are breaking out of, and + // then in process_deferred_edges we will map this hir_id to its PostOrderId, which + // will refer to the end of the block due to the post order traversal. + self.find_target_expression_from_destination(destination).map_or((), |target| { + self.drop_ranges.add_control_edge_hir_id(self.expr_index, target) + }) } ExprKind::Call(f, args) => { @@ -399,11 +444,9 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { | ExprKind::Binary(..) | ExprKind::Block(..) | ExprKind::Box(..) - | ExprKind::Break(..) | ExprKind::Cast(..) | ExprKind::Closure(..) | ExprKind::ConstBlock(..) - | ExprKind::Continue(..) | ExprKind::DropTemps(..) | ExprKind::Err | ExprKind::Field(..) diff --git a/src/test/ui/generator/drop-control-flow.rs b/src/test/ui/generator/drop-control-flow.rs index 6319a29f5b7d0..9e3fa7d2e98da 100644 --- a/src/test/ui/generator/drop-control-flow.rs +++ b/src/test/ui/generator/drop-control-flow.rs @@ -110,6 +110,22 @@ fn nested_loop() { }; } +fn loop_continue(b: bool) { + let _ = || { + let mut arr = [Ptr]; + let mut count = 0; + drop(arr); + while count < 3 { + count += 1; + yield; + if b { + arr = [Ptr]; + continue; + } + } + }; +} + fn main() { one_armed_if(true); if_let(Some(41)); @@ -118,4 +134,5 @@ fn main() { reinit(); loop_uninit(); nested_loop(); + loop_continue(true); }