Skip to content

Async fn resume after completion #66321

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Nov 29, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/librustc/mir/interpret/error.rs
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ use rustc_macros::HashStable;
use rustc_target::spec::abi::Abi;
use syntax_pos::{Pos, Span};
use syntax::symbol::Symbol;

use hir::GeneratorKind;
use std::{fmt, env};

use rustc_error_codes::*;
@@ -264,8 +264,8 @@ pub enum PanicInfo<O> {
OverflowNeg,
DivisionByZero,
RemainderByZero,
GeneratorResumedAfterReturn,
GeneratorResumedAfterPanic,
ResumedAfterReturn(GeneratorKind),
ResumedAfterPanic(GeneratorKind),
}

/// Type for MIR `Assert` terminator error messages.
@@ -300,10 +300,14 @@ impl<O> PanicInfo<O> {
"attempt to divide by zero",
RemainderByZero =>
"attempt to calculate the remainder with a divisor of zero",
GeneratorResumedAfterReturn =>
ResumedAfterReturn(GeneratorKind::Gen) =>
"generator resumed after completion",
GeneratorResumedAfterPanic =>
ResumedAfterReturn(GeneratorKind::Async(_)) =>
"`async fn` resumed after completion",
ResumedAfterPanic(GeneratorKind::Gen) =>
"generator resumed after panicking",
ResumedAfterPanic(GeneratorKind::Async(_)) =>
"`async fn` resumed after panicking",
Panic { .. } | BoundsCheck { .. } =>
bug!("Unexpected PanicInfo"),
}
22 changes: 19 additions & 3 deletions src/librustc/mir/mod.rs
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
use crate::hir::def::{CtorKind, Namespace};
use crate::hir::def_id::DefId;
use crate::hir;
use crate::hir::{self, GeneratorKind};
use crate::mir::interpret::{GlobalAlloc, PanicInfo, Scalar};
use crate::mir::visit::MirVisitable;
use crate::ty::adjustment::PointerCast;
@@ -117,6 +117,10 @@ pub struct Body<'tcx> {
/// The layout of a generator. Produced by the state transformation.
pub generator_layout: Option<GeneratorLayout<'tcx>>,

/// If this is a generator then record the type of source expression that caused this generator
/// to be created.
pub generator_kind: Option<GeneratorKind>,

/// Declarations of locals.
///
/// The first local is the return value pointer, followed by `arg_count`
@@ -170,6 +174,7 @@ impl<'tcx> Body<'tcx> {
var_debug_info: Vec<VarDebugInfo<'tcx>>,
span: Span,
control_flow_destroyed: Vec<(Span, String)>,
generator_kind : Option<GeneratorKind>,
) -> Self {
// We need `arg_count` locals, and one for the return place.
assert!(
@@ -187,6 +192,7 @@ impl<'tcx> Body<'tcx> {
yield_ty: None,
generator_drop: None,
generator_layout: None,
generator_kind,
local_decls,
user_type_annotations,
arg_count,
@@ -2975,7 +2981,7 @@ impl<'tcx> TypeFoldable<'tcx> for Terminator<'tcx> {
index: index.fold_with(folder),
},
Panic { .. } | Overflow(_) | OverflowNeg | DivisionByZero | RemainderByZero |
GeneratorResumedAfterReturn | GeneratorResumedAfterPanic =>
ResumedAfterReturn(_) | ResumedAfterPanic(_) =>
msg.clone(),
};
Assert { cond: cond.fold_with(folder), expected, msg, target, cleanup }
@@ -3021,7 +3027,7 @@ impl<'tcx> TypeFoldable<'tcx> for Terminator<'tcx> {
len.visit_with(visitor) || index.visit_with(visitor),
Panic { .. } | Overflow(_) | OverflowNeg |
DivisionByZero | RemainderByZero |
GeneratorResumedAfterReturn | GeneratorResumedAfterPanic =>
ResumedAfterReturn(_) | ResumedAfterPanic(_) =>
false
}
} else {
@@ -3040,6 +3046,16 @@ impl<'tcx> TypeFoldable<'tcx> for Terminator<'tcx> {
}
}

impl<'tcx> TypeFoldable<'tcx> for GeneratorKind {
fn super_fold_with<F: TypeFolder<'tcx>>(&self, _: &mut F) -> Self {
*self
}

fn super_visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> bool {
false
}
}

impl<'tcx> TypeFoldable<'tcx> for Place<'tcx> {
fn super_fold_with<F: TypeFolder<'tcx>>(&self, folder: &mut F) -> Self {
Place {
2 changes: 1 addition & 1 deletion src/librustc/mir/visit.rs
Original file line number Diff line number Diff line change
@@ -517,7 +517,7 @@ macro_rules! make_mir_visitor {
self.visit_operand(index, location);
}
Panic { .. } | Overflow(_) | OverflowNeg | DivisionByZero | RemainderByZero |
GeneratorResumedAfterReturn | GeneratorResumedAfterPanic => {
ResumedAfterReturn(_) | ResumedAfterPanic(_) => {
// Nothing to visit
}
}
15 changes: 8 additions & 7 deletions src/librustc_mir/build/mod.rs
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ use crate::hair::{LintLevel, BindingMode, PatKind};
use crate::transform::MirSource;
use crate::util as mir_util;
use rustc::hir;
use rustc::hir::Node;
use rustc::hir::{Node, GeneratorKind};
use rustc::hir::def_id::DefId;
use rustc::middle::lang_items;
use rustc::middle::region;
@@ -279,7 +279,7 @@ struct Builder<'a, 'tcx> {

fn_span: Span,
arg_count: usize,
is_generator: bool,
generator_kind: Option<GeneratorKind>,

/// The current set of scopes, updated as we traverse;
/// see the `scope` module for more details.
@@ -570,7 +570,7 @@ where
safety,
return_ty,
return_ty_span,
body.generator_kind.is_some());
body.generator_kind);

let call_site_scope = region::Scope {
id: body.value.hir_id.local_id,
@@ -647,7 +647,7 @@ fn construct_const<'a, 'tcx>(
Safety::Safe,
const_ty,
const_ty_span,
false,
None,
);

let mut block = START_BLOCK;
@@ -678,7 +678,7 @@ fn construct_error<'a, 'tcx>(
let owner_id = hir.tcx().hir().body_owner(body_id);
let span = hir.tcx().hir().span(owner_id);
let ty = hir.tcx().types.err;
let mut builder = Builder::new(hir, span, 0, Safety::Safe, ty, span, false);
let mut builder = Builder::new(hir, span, 0, Safety::Safe, ty, span, None);
let source_info = builder.source_info(span);
builder.cfg.terminate(START_BLOCK, source_info, TerminatorKind::Unreachable);
builder.finish()
@@ -691,15 +691,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
safety: Safety,
return_ty: Ty<'tcx>,
return_span: Span,
is_generator: bool)
generator_kind: Option<GeneratorKind>)
-> Builder<'a, 'tcx> {
let lint_level = LintLevel::Explicit(hir.root_lint_level);
let mut builder = Builder {
hir,
cfg: CFG { basic_blocks: IndexVec::new() },
fn_span: span,
arg_count,
is_generator,
generator_kind,
scopes: Default::default(),
block_context: BlockContext::new(),
source_scopes: IndexVec::new(),
@@ -748,6 +748,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
self.var_debug_info,
self.fn_span,
self.hir.control_flow_destroyed(),
self.generator_kind
)
}

40 changes: 23 additions & 17 deletions src/librustc_mir/build/scope.rs
Original file line number Diff line number Diff line change
@@ -91,6 +91,7 @@ use syntax_pos::{DUMMY_SP, Span};
use rustc_data_structures::fx::FxHashMap;
use std::collections::hash_map::Entry;
use std::mem;
use rustc::hir::GeneratorKind;

#[derive(Debug)]
struct Scope {
@@ -219,7 +220,12 @@ impl Scope {
/// `storage_only` controls whether to invalidate only drop paths that run `StorageDead`.
/// `this_scope_only` controls whether to invalidate only drop paths that refer to the current
/// top-of-scope (as opposed to dependent scopes).
fn invalidate_cache(&mut self, storage_only: bool, is_generator: bool, this_scope_only: bool) {
fn invalidate_cache(
&mut self,
storage_only: bool,
generator_kind: Option<GeneratorKind>,
this_scope_only: bool
) {
// FIXME: maybe do shared caching of `cached_exits` etc. to handle functions
// with lots of `try!`?

@@ -229,7 +235,7 @@ impl Scope {
// the current generator drop and unwind refer to top-of-scope
self.cached_generator_drop = None;

let ignore_unwinds = storage_only && !is_generator;
let ignore_unwinds = storage_only && generator_kind.is_none();
if !ignore_unwinds {
self.cached_unwind.invalidate();
}
@@ -481,7 +487,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {

unpack!(block = build_scope_drops(
&mut self.cfg,
self.is_generator,
self.generator_kind,
&scope,
block,
unwind_to,
@@ -574,7 +580,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {

unpack!(block = build_scope_drops(
&mut self.cfg,
self.is_generator,
self.generator_kind,
scope,
block,
unwind_to,
@@ -625,7 +631,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {

unpack!(block = build_scope_drops(
&mut self.cfg,
self.is_generator,
self.generator_kind,
scope,
block,
unwind_to,
@@ -809,7 +815,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// invalidating caches of each scope visited. This way bare minimum of the
// caches gets invalidated. i.e., if a new drop is added into the middle scope, the
// cache of outer scope stays intact.
scope.invalidate_cache(!needs_drop, self.is_generator, this_scope);
scope.invalidate_cache(!needs_drop, self.generator_kind, this_scope);
if this_scope {
let region_scope_span = region_scope.span(self.hir.tcx(),
&self.hir.region_scope_tree);
@@ -958,7 +964,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
}

top_scope.invalidate_cache(true, self.is_generator, true);
top_scope.invalidate_cache(true, self.generator_kind, true);
} else {
bug!("Expected as_local_operand to produce a temporary");
}
@@ -1016,7 +1022,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {

for scope in self.scopes.top_scopes(first_uncached) {
target = build_diverge_scope(&mut self.cfg, scope.region_scope_span,
scope, target, generator_drop, self.is_generator);
scope, target, generator_drop, self.generator_kind);
}

target
@@ -1079,14 +1085,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
assert_eq!(top_scope.region_scope, region_scope);

top_scope.drops.clear();
top_scope.invalidate_cache(false, self.is_generator, true);
top_scope.invalidate_cache(false, self.generator_kind, true);
}
}

/// Builds drops for pop_scope and exit_scope.
fn build_scope_drops<'tcx>(
cfg: &mut CFG<'tcx>,
is_generator: bool,
generator_kind: Option<GeneratorKind>,
scope: &Scope,
mut block: BasicBlock,
last_unwind_to: BasicBlock,
@@ -1130,7 +1136,7 @@ fn build_scope_drops<'tcx>(
continue;
}

let unwind_to = get_unwind_to(scope, is_generator, drop_idx, generator_drop)
let unwind_to = get_unwind_to(scope, generator_kind, drop_idx, generator_drop)
.unwrap_or(last_unwind_to);

let next = cfg.start_new_block();
@@ -1156,19 +1162,19 @@ fn build_scope_drops<'tcx>(

fn get_unwind_to(
scope: &Scope,
is_generator: bool,
generator_kind: Option<GeneratorKind>,
unwind_from: usize,
generator_drop: bool,
) -> Option<BasicBlock> {
for drop_idx in (0..unwind_from).rev() {
let drop_data = &scope.drops[drop_idx];
match (is_generator, &drop_data.kind) {
(true, DropKind::Storage) => {
match (generator_kind, &drop_data.kind) {
(Some(_), DropKind::Storage) => {
return Some(drop_data.cached_block.get(generator_drop).unwrap_or_else(|| {
span_bug!(drop_data.span, "cached block not present for {:?}", drop_data)
}));
}
(false, DropKind::Value) => {
(None, DropKind::Value) => {
return Some(drop_data.cached_block.get(generator_drop).unwrap_or_else(|| {
span_bug!(drop_data.span, "cached block not present for {:?}", drop_data)
}));
@@ -1184,7 +1190,7 @@ fn build_diverge_scope<'tcx>(cfg: &mut CFG<'tcx>,
scope: &mut Scope,
mut target: BasicBlock,
generator_drop: bool,
is_generator: bool)
generator_kind: Option<GeneratorKind>)
-> BasicBlock
{
// Build up the drops in **reverse** order. The end result will
@@ -1224,7 +1230,7 @@ fn build_diverge_scope<'tcx>(cfg: &mut CFG<'tcx>,
// match the behavior of clang, but on inspection eddyb says
// this is not what clang does.
match drop_data.kind {
DropKind::Storage if is_generator => {
DropKind::Storage if generator_kind.is_some() => {
storage_deads.push(Statement {
source_info: source_info(drop_data.span),
kind: StatementKind::StorageDead(drop_data.local)
6 changes: 4 additions & 2 deletions src/librustc_mir/interpret/terminator.rs
Original file line number Diff line number Diff line change
@@ -142,8 +142,10 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
OverflowNeg => err_panic!(OverflowNeg),
DivisionByZero => err_panic!(DivisionByZero),
RemainderByZero => err_panic!(RemainderByZero),
GeneratorResumedAfterReturn => err_panic!(GeneratorResumedAfterReturn),
GeneratorResumedAfterPanic => err_panic!(GeneratorResumedAfterPanic),
ResumedAfterReturn(generator_kind)
=> err_panic!(ResumedAfterReturn(*generator_kind)),
ResumedAfterPanic(generator_kind)
=> err_panic!(ResumedAfterPanic(*generator_kind)),
Panic { .. } => bug!("`Panic` variant cannot occur in MIR"),
}
.into());
55 changes: 30 additions & 25 deletions src/librustc_mir/shim.rs
Original file line number Diff line number Diff line change
@@ -196,19 +196,14 @@ fn build_drop_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, ty: Option<Ty<'tcx>>)
block(&mut blocks, TerminatorKind::Goto { target: return_block });
block(&mut blocks, TerminatorKind::Return);

let mut body = Body::new(
let mut body = new_body(
blocks,
IndexVec::from_elem_n(
SourceScopeData { span: span, parent_scope: None }, 1
SourceScopeData { span, parent_scope: None }, 1
),
ClearCrossCrate::Clear,
local_decls_for_sig(&sig, span),
IndexVec::new(),
sig.inputs().len(),
vec![],
span,
vec![],
);
span);

if let Some(..) = ty {
// The first argument (index 0), but add 1 for the return value.
@@ -247,6 +242,27 @@ fn build_drop_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, ty: Option<Ty<'tcx>>)
body
}

fn new_body<'tcx>(
basic_blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>,
source_scopes: IndexVec<SourceScope, SourceScopeData>,
local_decls: IndexVec<Local, LocalDecl<'tcx>>,
arg_count: usize,
span: Span,
) -> Body<'tcx> {
Body::new(
basic_blocks,
source_scopes,
ClearCrossCrate::Clear,
local_decls,
IndexVec::new(),
arg_count,
vec![],
span,
vec![],
None,
)
}

pub struct DropShimElaborator<'a, 'tcx> {
pub body: &'a Body<'tcx>,
pub patch: MirPatch<'tcx>,
@@ -362,18 +378,14 @@ impl CloneShimBuilder<'tcx> {
}

fn into_mir(self) -> Body<'tcx> {
Body::new(
new_body(
self.blocks,
IndexVec::from_elem_n(
SourceScopeData { span: self.span, parent_scope: None }, 1
),
ClearCrossCrate::Clear,
self.local_decls,
IndexVec::new(),
self.sig.inputs().len(),
vec![],
self.span,
vec![],
)
}

@@ -822,19 +834,16 @@ fn build_call_shim<'tcx>(
block(&mut blocks, vec![], TerminatorKind::Resume, true);
}

let mut body = Body::new(
let mut body = new_body(
blocks,
IndexVec::from_elem_n(
SourceScopeData { span: span, parent_scope: None }, 1
SourceScopeData { span, parent_scope: None }, 1
),
ClearCrossCrate::Clear,
local_decls,
IndexVec::new(),
sig.inputs().len(),
vec![],
span,
vec![],
);

if let Abi::RustCall = sig.abi {
body.spread_arg = Some(Local::new(sig.inputs().len()));
}
@@ -908,18 +917,14 @@ pub fn build_adt_ctor(tcx: TyCtxt<'_>, ctor_id: DefId) -> &Body<'_> {
is_cleanup: false
};

let body = Body::new(
let body = new_body(
IndexVec::from_elem_n(start_block, 1),
IndexVec::from_elem_n(
SourceScopeData { span: span, parent_scope: None }, 1
SourceScopeData { span, parent_scope: None }, 1
),
ClearCrossCrate::Clear,
local_decls,
IndexVec::new(),
sig.inputs().len(),
vec![],
span,
vec![],
);

crate::util::dump_mir(
1 change: 1 addition & 0 deletions src/librustc_mir/transform/const_prop.rs
Original file line number Diff line number Diff line change
@@ -91,6 +91,7 @@ impl<'tcx> MirPass<'tcx> for ConstProp {
Default::default(),
tcx.def_span(source.def_id()),
Default::default(),
body.generator_kind,
);

// FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold
19 changes: 13 additions & 6 deletions src/librustc_mir/transform/generator.rs
Original file line number Diff line number Diff line change
@@ -1056,16 +1056,23 @@ fn create_generator_resume_function<'tcx>(
let mut cases = create_cases(body, &transform, |point| Some(point.resume));

use rustc::mir::interpret::PanicInfo::{
GeneratorResumedAfterPanic,
GeneratorResumedAfterReturn,
ResumedAfterPanic,
ResumedAfterReturn,
};

// Jump to the entry point on the unresumed
cases.insert(0, (UNRESUMED, BasicBlock::new(0)));
// Panic when resumed on the returned state
cases.insert(1, (RETURNED, insert_panic_block(tcx, body, GeneratorResumedAfterReturn)));
// Panic when resumed on the poisoned state
cases.insert(2, (POISONED, insert_panic_block(tcx, body, GeneratorResumedAfterPanic)));

// Panic when resumed on the returned or poisoned state
let generator_kind = body.generator_kind.unwrap();
cases.insert(1, (RETURNED, insert_panic_block(
tcx,
body,
ResumedAfterReturn(generator_kind))));
cases.insert(2, (POISONED, insert_panic_block(
tcx,
body,
ResumedAfterPanic(generator_kind))));

insert_switch(body, cases, &transform, TerminatorKind::Unreachable);

1 change: 1 addition & 0 deletions src/librustc_mir/transform/promote_consts.rs
Original file line number Diff line number Diff line change
@@ -1104,6 +1104,7 @@ pub fn promote_candidates<'tcx>(
vec![],
body.span,
vec![],
body.generator_kind,
),
tcx,
source: body,
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// issue 65419 - Attempting to run an async fn after completion mentions generators when it should
// be talking about `async fn`s instead.

// run-fail
// error-pattern: thread 'main' panicked at '`async fn` resumed after completion'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... the error is RuntimeError: unreachable. Check whether any tests check for that error pattern, or if there are any wasm panic tests and how to check them. I think it would also be OK to just add // ignore-wasm to these tests if there's no way to specify different error patterns for different platforms

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Assuming that the ignore-wasm comment is how this is done.) Search for it in the test suite, if it exists, that's it, if not, find out how to disable tests on wasm by searching for "wasm"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason it's OK to disable the tests on wasm is that here we only want to see a specific panic message. We have tests to see that panicking in general works, so assuming panicking works and the tests work on most platforms, there's basically no way they'd be broken on wasm. Wasn't just generally doesn't show the panic message, but that's not something you need to solve in this PR

// edition:2018
// ignore-wasm no panic or subprocess support
// ignore-emscripten no panic or subprocess support

#![feature(generators, generator_trait)]

async fn foo() {
}

fn main() {
let mut future = Box::pin(foo());
executor::block_on(future.as_mut());
executor::block_on(future.as_mut());
}

mod executor {
use core::{
future::Future,
pin::Pin,
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};

pub fn block_on<F: Future>(mut future: F) -> F::Output {
let mut future = unsafe { Pin::new_unchecked(&mut future) };

static VTABLE: RawWakerVTable = RawWakerVTable::new(
|_| unimplemented!("clone"),
|_| unimplemented!("wake"),
|_| unimplemented!("wake_by_ref"),
|_| (),
);
let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) };
let mut context = Context::from_waker(&waker);

loop {
if let Poll::Ready(val) = future.as_mut().poll(&mut context) {
break val;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// issue 65419 - Attempting to run an async fn after completion mentions generators when it should
// be talking about `async fn`s instead. Should also test what happens when it panics.

// run-fail
// error-pattern: thread 'main' panicked at '`async fn` resumed after panicking'
// edition:2018
// ignore-wasm no panic or subprocess support
// ignore-emscripten no panic or subprocess support

#![feature(generators, generator_trait)]

use std::panic;

async fn foo() {
panic!();
}

fn main() {
let mut future = Box::pin(foo());
panic::catch_unwind(panic::AssertUnwindSafe(|| {
executor::block_on(future.as_mut());
}));

executor::block_on(future.as_mut());
}

mod executor {
use core::{
future::Future,
pin::Pin,
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};

pub fn block_on<F: Future>(mut future: F) -> F::Output {
let mut future = unsafe { Pin::new_unchecked(&mut future) };

static VTABLE: RawWakerVTable = RawWakerVTable::new(
|_| unimplemented!("clone"),
|_| unimplemented!("wake"),
|_| unimplemented!("wake_by_ref"),
|_| (),
);
let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) };
let mut context = Context::from_waker(&waker);

loop {
if let Poll::Ready(val) = future.as_mut().poll(&mut context) {
break val;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// issue 65419 - Attempting to run an `async fn` after completion mentions generators when it should
// be talking about `async fn`s instead. Regression test added to make sure generators still
// panic when resumed after completion.

// run-fail
// error-pattern:generator resumed after completion
// edition:2018
// ignore-wasm no panic or subprocess support
// ignore-emscripten no panic or subprocess support

#![feature(generators, generator_trait)]

use std::{
ops::Generator,
pin::Pin,
};

fn main() {
let mut g = || {
yield;
};
Pin::new(&mut g).resume(); // Yields once.
Pin::new(&mut g).resume(); // Completes here.
Pin::new(&mut g).resume(); // Panics here.
}