-
-
Save joonazan/32fe6627e320325e7182c072ff8461d0 to your computer and use it in GitHub Desktop.
Minimal version of bad drop codegen for coproduct
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::any::TypeId; | |
use std::mem::ManuallyDrop; | |
#[repr(C)] | |
pub union Union<A, B> { | |
pub(crate) head: ManuallyDrop<A>, | |
pub(crate) tail: ManuallyDrop<B>, | |
} | |
pub enum EmptyUnion {} | |
/// Trait for properly deallocating Unions that are not Copy. | |
/// | |
/// Unlike the other Indexed* traits, this one is exported because there is no | |
/// way to avoid mentioning it. For example to require IndexedClone it suffices | |
/// to require that `Coproduct<T>` is [Clone]. | |
pub trait IndexedDrop { | |
/// # Safety | |
/// The argument `i` must be the type id of the type stored in the Union. | |
unsafe fn idrop(&mut self, i: TypeId); | |
} | |
impl<H: 'static, T: IndexedDrop> IndexedDrop for Union<H, T> { | |
unsafe fn idrop(&mut self, i: TypeId) { | |
if i == TypeId::of::<H>() { | |
ManuallyDrop::drop(&mut self.head) | |
} else { | |
self.tail.idrop(i) | |
} | |
} | |
} | |
impl IndexedDrop for EmptyUnion { | |
#[inline] | |
unsafe fn idrop(&mut self, _: TypeId) { | |
match *self {} | |
} | |
} | |
/// Changes type to ANYTHING. | |
/// # Safety | |
/// Only use this on repr(C) unions. The output union must be able to contain | |
/// the active variant of the input union. | |
pub unsafe fn union_transmute<X, Y>(x: X) -> Y { | |
#[repr(C)] | |
union Transmuter<A, B> { | |
before: ManuallyDrop<A>, | |
after: ManuallyDrop<B>, | |
} | |
ManuallyDrop::into_inner( | |
Transmuter { | |
before: ManuallyDrop::new(x), | |
} | |
.after, | |
) | |
} | |
/// This trait is implemented for Unions where variant I has type X. | |
pub trait UnionAt<I, X> { | |
/// Create a union that contains the given value. | |
fn inject(x: X) -> Self; | |
/// Convert a union to the contained type. | |
/// # Safety | |
/// If the active variant of the coproduct is not at index I, | |
/// calling this method is undefined behaviour. | |
unsafe fn take(self) -> X; | |
/// The coproduct minus its Ith variant | |
type Pruned; | |
} | |
pub struct Here; | |
pub struct There<T>(T); | |
impl<X, Rest> UnionAt<Here, X> for Union<X, Rest> { | |
fn inject(x: X) -> Self { | |
Union { | |
head: ManuallyDrop::new(x), | |
} | |
} | |
unsafe fn take(self) -> X { | |
ManuallyDrop::into_inner(self.head) | |
} | |
type Pruned = Rest; | |
} | |
impl<I, X, H, T> UnionAt<There<I>, X> for Union<H, T> | |
where | |
T: UnionAt<I, X>, | |
{ | |
fn inject(x: X) -> Self { | |
Union { | |
tail: ManuallyDrop::new(T::inject(x)), | |
} | |
} | |
unsafe fn take(self) -> X { | |
ManuallyDrop::into_inner(self.tail).take() | |
} | |
type Pruned = Union<H, T::Pruned>; | |
} | |
pub struct Coproduct<T: IndexedDrop> { | |
tag: TypeId, | |
union: T, | |
} | |
impl<T: IndexedDrop> Drop for Coproduct<T> { | |
fn drop(&mut self) { | |
unsafe { self.union.idrop(self.tag) } | |
} | |
} | |
pub trait At<I, X> { | |
fn inject(x: X) -> Self; | |
fn uninject(self) -> Result<X, Self::Pruned>; | |
type Pruned; | |
} | |
impl<I, X: 'static, U: IndexedDrop> At<I, X> for Coproduct<U> | |
where | |
U: UnionAt<I, X>, | |
U::Pruned: IndexedDrop, | |
{ | |
fn inject(x: X) -> Self { | |
Self { | |
tag: TypeId::of::<X>(), | |
union: U::inject(x), | |
} | |
} | |
fn uninject(self) -> Result<X, Self::Pruned> { | |
let res = if self.tag == TypeId::of::<X>() { | |
Ok(unsafe { std::ptr::read(&self.union).take() }) | |
} else { | |
Err(Coproduct { | |
tag: self.tag, | |
union: unsafe { union_transmute(std::ptr::read(&self.union)) }, | |
}) | |
}; | |
std::mem::forget(self); | |
res | |
} | |
type Pruned = Coproduct<U::Pruned>; | |
} | |
type TestCoprod = Coproduct<Union<u8, Union<u16, Union<u32, Union<i32, EmptyUnion>>>>>; | |
pub fn uninject_coprod(c: TestCoprod) -> Option<u32> { | |
c.uninject().ok() | |
} | |
pub fn drop_coprod(c: TestCoprod) {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment