| //@revisions: stack tree |
| //@[tree]compile-flags: -Zmiri-tree-borrows |
| #![feature(coroutines, coroutine_trait, never_type, stmt_expr_attributes)] |
| |
| use std::fmt::Debug; |
| use std::mem::ManuallyDrop; |
| use std::ops::Coroutine; |
| use std::ops::CoroutineState::{self, *}; |
| use std::pin::Pin; |
| use std::ptr; |
| use std::sync::atomic::{AtomicUsize, Ordering}; |
| |
| fn basic() { |
| fn finish<T>(mut amt: usize, self_referential: bool, mut t: T) -> T::Return |
| where |
| T: Coroutine<Yield = usize>, |
| { |
| // We are not moving the `t` around until it gets dropped, so this is okay. |
| let mut t = unsafe { Pin::new_unchecked(&mut t) }; |
| loop { |
| let state = t.as_mut().resume(()); |
| // Test if the coroutine is valid (according to type invariants). |
| // For self-referential coroutines however this is UB! |
| if !self_referential { |
| let _ = unsafe { ManuallyDrop::new(ptr::read(t.as_mut().get_unchecked_mut())) }; |
| } |
| match state { |
| CoroutineState::Yielded(y) => { |
| amt -= y; |
| } |
| CoroutineState::Complete(ret) => { |
| assert_eq!(amt, 0); |
| return ret; |
| } |
| } |
| } |
| } |
| |
| enum Never {} |
| fn never() -> Never { |
| panic!() |
| } |
| |
| finish( |
| 1, |
| false, |
| #[coroutine] |
| || yield 1, |
| ); |
| |
| finish( |
| 3, |
| false, |
| #[coroutine] |
| || { |
| let mut x = 0; |
| yield 1; |
| x += 1; |
| yield 1; |
| x += 1; |
| yield 1; |
| assert_eq!(x, 2); |
| }, |
| ); |
| |
| finish( |
| 7 * 8 / 2, |
| false, |
| #[coroutine] |
| || { |
| for i in 0..8 { |
| yield i; |
| } |
| }, |
| ); |
| |
| finish( |
| 1, |
| false, |
| #[coroutine] |
| || { |
| if true { |
| yield 1; |
| } else { |
| } |
| }, |
| ); |
| |
| finish( |
| 1, |
| false, |
| #[coroutine] |
| || { |
| if false { |
| } else { |
| yield 1; |
| } |
| }, |
| ); |
| |
| finish( |
| 2, |
| false, |
| #[coroutine] |
| || { |
| if { |
| yield 1; |
| false |
| } { |
| yield 1; |
| panic!() |
| } |
| yield 1; |
| }, |
| ); |
| |
| // also test self-referential coroutines |
| assert_eq!( |
| finish( |
| 5, |
| true, |
| #[coroutine] |
| static || { |
| let mut x = 5; |
| let y = &mut x; |
| *y = 5; |
| yield *y; |
| *y = 10; |
| x |
| } |
| ), |
| 10 |
| ); |
| assert_eq!( |
| finish( |
| 5, |
| true, |
| #[coroutine] |
| || { |
| let mut x = Box::new(5); |
| let y = &mut *x; |
| *y = 5; |
| yield *y; |
| *y = 10; |
| *x |
| } |
| ), |
| 10 |
| ); |
| |
| let b = true; |
| finish( |
| 1, |
| false, |
| #[coroutine] |
| || { |
| yield 1; |
| if b { |
| return; |
| } |
| #[allow(unused)] |
| let x = never(); |
| #[allow(unreachable_code)] |
| yield 2; |
| drop(x); |
| }, |
| ); |
| |
| finish( |
| 3, |
| false, |
| #[coroutine] |
| || { |
| yield 1; |
| #[allow(unreachable_code)] |
| let _x: (String, !) = (String::new(), { |
| yield 2; |
| return; |
| }); |
| }, |
| ); |
| } |
| |
| fn smoke_resume_arg() { |
| fn drain<G: Coroutine<R, Yield = Y> + Unpin, R, Y>( |
| gen_: &mut G, |
| inout: Vec<(R, CoroutineState<Y, G::Return>)>, |
| ) where |
| Y: Debug + PartialEq, |
| G::Return: Debug + PartialEq, |
| { |
| let mut gen_ = Pin::new(gen_); |
| |
| for (input, out) in inout { |
| assert_eq!(gen_.as_mut().resume(input), out); |
| // Test if the coroutine is valid (according to type invariants). |
| let _ = unsafe { ManuallyDrop::new(ptr::read(gen_.as_mut().get_unchecked_mut())) }; |
| } |
| } |
| |
| static DROPS: AtomicUsize = AtomicUsize::new(0); |
| |
| #[derive(Debug, PartialEq)] |
| struct DropMe; |
| |
| impl Drop for DropMe { |
| fn drop(&mut self) { |
| DROPS.fetch_add(1, Ordering::SeqCst); |
| } |
| } |
| |
| fn expect_drops<T>(expected_drops: usize, f: impl FnOnce() -> T) -> T { |
| DROPS.store(0, Ordering::SeqCst); |
| |
| let res = f(); |
| |
| let actual_drops = DROPS.load(Ordering::SeqCst); |
| assert_eq!(actual_drops, expected_drops); |
| res |
| } |
| |
| drain( |
| &mut #[coroutine] |
| |mut b| { |
| while b != 0 { |
| b = yield (b + 1); |
| } |
| -1 |
| }, |
| vec![(1, Yielded(2)), (-45, Yielded(-44)), (500, Yielded(501)), (0, Complete(-1))], |
| ); |
| |
| expect_drops(2, || { |
| drain( |
| &mut #[coroutine] |
| |a| yield a, |
| vec![(DropMe, Yielded(DropMe))], |
| ) |
| }); |
| |
| expect_drops(6, || { |
| drain( |
| &mut #[coroutine] |
| |a| yield yield a, |
| vec![(DropMe, Yielded(DropMe)), (DropMe, Yielded(DropMe)), (DropMe, Complete(DropMe))], |
| ) |
| }); |
| |
| #[allow(unreachable_code)] |
| expect_drops(2, || { |
| drain( |
| &mut #[coroutine] |
| |a| yield return a, |
| vec![(DropMe, Complete(DropMe))], |
| ) |
| }); |
| |
| expect_drops(2, || { |
| drain( |
| &mut #[coroutine] |
| |a: DropMe| { |
| if false { yield () } else { a } |
| }, |
| vec![(DropMe, Complete(DropMe))], |
| ) |
| }); |
| |
| expect_drops(4, || { |
| drain( |
| #[allow(unused_assignments, unused_variables)] |
| &mut #[coroutine] |
| |mut a: DropMe| { |
| a = yield; |
| a = yield; |
| a = yield; |
| }, |
| vec![ |
| (DropMe, Yielded(())), |
| (DropMe, Yielded(())), |
| (DropMe, Yielded(())), |
| (DropMe, Complete(())), |
| ], |
| ) |
| }); |
| } |
| |
| fn uninit_fields() { |
| // Test that uninhabited saved local doesn't make the entire variant uninhabited. |
| // (https://github.com/rust-lang/rust/issues/115145, https://github.com/rust-lang/rust/pull/118871) |
| fn conjure<T>() -> T { |
| loop {} |
| } |
| |
| fn run<T>(x: bool, y: bool) { |
| let mut c = #[coroutine] |
| || { |
| if x { |
| let _a: T; |
| if y { |
| _a = conjure::<T>(); |
| } |
| yield (); |
| } else { |
| let _a: T; |
| if y { |
| _a = conjure::<T>(); |
| } |
| yield (); |
| } |
| }; |
| assert!(matches!(Pin::new(&mut c).resume(()), CoroutineState::Yielded(()))); |
| assert!(matches!(Pin::new(&mut c).resume(()), CoroutineState::Complete(()))); |
| } |
| |
| run::<!>(false, false); |
| } |
| |
| fn main() { |
| basic(); |
| smoke_resume_arg(); |
| uninit_fields(); |
| } |