use super::{Class, JsClass};
use crate::{result::BorrowError, Ctx, Error, FromJs, IntoJs, Value};
use std::{
cell::{Cell, UnsafeCell},
marker::PhantomData,
mem::ManuallyDrop,
ops::{Deref, DerefMut},
};
pub unsafe trait Mutability {
#[doc(hidden)]
type Cell<T>;
#[doc(hidden)]
fn new_cell<T>(t: T) -> Self::Cell<T>;
#[doc(hidden)]
unsafe fn borrow<'a, T>(cell: &'a Self::Cell<T>) -> Result<(), BorrowError>;
#[doc(hidden)]
unsafe fn unborrow<'a, T>(cell: &'a Self::Cell<T>);
#[doc(hidden)]
unsafe fn borrow_mut<'a, T>(cell: &'a Self::Cell<T>) -> Result<(), BorrowError>;
#[doc(hidden)]
unsafe fn unborrow_mut<'a, T>(cell: &'a Self::Cell<T>);
#[doc(hidden)]
unsafe fn deref<'a, T>(cell: &'a Self::Cell<T>) -> &'a T;
#[doc(hidden)]
#[allow(clippy::mut_from_ref)]
unsafe fn deref_mut<'a, T>(cell: &'a Self::Cell<T>) -> &'a mut T;
}
pub enum Readable {}
unsafe impl Mutability for Readable {
type Cell<T> = T;
fn new_cell<T>(t: T) -> Self::Cell<T> {
t
}
unsafe fn borrow<'a, T>(_cell: &'a Self::Cell<T>) -> Result<(), BorrowError> {
Ok(())
}
unsafe fn unborrow<'a, T>(_cell: &'a Self::Cell<T>) {}
unsafe fn borrow_mut<'a, T>(_cell: &'a Self::Cell<T>) -> Result<(), BorrowError> {
Err(BorrowError::NotWritable)
}
unsafe fn unborrow_mut<'a, T>(_cell: &'a Self::Cell<T>) {}
unsafe fn deref<'a, T>(cell: &'a Self::Cell<T>) -> &'a T {
cell
}
unsafe fn deref_mut<'a, T>(_cell: &'a Self::Cell<T>) -> &'a mut T {
unreachable!()
}
}
pub enum Writable {}
pub struct WritableCell<T> {
count: Cell<usize>,
value: UnsafeCell<T>,
}
#[doc(hidden)]
pub struct WriteBorrow<'a, T> {
cell: &'a WritableCell<T>,
_marker: PhantomData<&'a T>,
}
impl<'a, T> Deref for WriteBorrow<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &(*self.cell.value.get()) }
}
}
impl<'a, T> Drop for WriteBorrow<'a, T> {
fn drop(&mut self) {
self.cell.count.set(self.cell.count.get() - 1);
}
}
#[doc(hidden)]
pub struct WriteBorrowMut<'a, T> {
cell: &'a WritableCell<T>,
_marker: PhantomData<&'a T>,
}
impl<'a, T> Deref for WriteBorrowMut<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &(*self.cell.value.get()) }
}
}
impl<'a, T> DerefMut for WriteBorrowMut<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut (*self.cell.value.get()) }
}
}
impl<'a, T> Drop for WriteBorrowMut<'a, T> {
fn drop(&mut self) {
self.cell.count.set(0);
}
}
unsafe impl Mutability for Writable {
type Cell<T> = WritableCell<T>;
fn new_cell<T>(t: T) -> Self::Cell<T> {
WritableCell {
count: Cell::new(0),
value: UnsafeCell::new(t),
}
}
unsafe fn borrow<'a, T>(cell: &'a Self::Cell<T>) -> Result<(), BorrowError> {
let count = cell.count.get();
if count == usize::MAX {
return Err(BorrowError::AlreadyBorrowed);
}
cell.count.set(count + 1);
Ok(())
}
unsafe fn unborrow<'a, T>(cell: &'a Self::Cell<T>) {
cell.count.set(cell.count.get() - 1);
}
unsafe fn borrow_mut<'a, T>(cell: &'a Self::Cell<T>) -> Result<(), BorrowError> {
let count = cell.count.get();
if count != 0 {
return Err(BorrowError::AlreadyBorrowed);
}
cell.count.set(usize::MAX);
Ok(())
}
unsafe fn unborrow_mut<'a, T>(cell: &'a Self::Cell<T>) {
cell.count.set(0);
}
unsafe fn deref<'a, T>(cell: &'a Self::Cell<T>) -> &'a T {
&*cell.value.get()
}
unsafe fn deref_mut<'a, T>(cell: &'a Self::Cell<T>) -> &'a mut T {
&mut *cell.value.get()
}
}
pub struct JsCell<'js, T: JsClass<'js>> {
pub(crate) cell: <T::Mutable as Mutability>::Cell<T>,
}
impl<'js, T: JsClass<'js>> JsCell<'js, T> {
pub fn new(t: T) -> Self {
JsCell {
cell: <T::Mutable as Mutability>::new_cell(t),
}
}
pub fn borrow<'a>(&'a self) -> Borrow<'a, 'js, T> {
unsafe {
<T::Mutable as Mutability>::borrow(&self.cell).unwrap();
Borrow(&self.cell)
}
}
pub fn try_borrow<'a>(&'a self) -> Result<Borrow<'a, 'js, T>, BorrowError> {
unsafe {
<T::Mutable as Mutability>::borrow(&self.cell)?;
Ok(Borrow(&self.cell))
}
}
pub fn borrow_mut<'a>(&'a self) -> BorrowMut<'a, 'js, T> {
unsafe {
<T::Mutable as Mutability>::borrow_mut(&self.cell).unwrap();
BorrowMut(&self.cell)
}
}
pub fn try_borrow_mut<'a>(&'a self) -> Result<BorrowMut<'a, 'js, T>, BorrowError> {
unsafe {
<T::Mutable as Mutability>::borrow_mut(&self.cell)?;
Ok(BorrowMut(&self.cell))
}
}
}
pub struct Borrow<'a, 'js, T: JsClass<'js> + 'a>(&'a <T::Mutable as Mutability>::Cell<T>);
impl<'a, 'js, T: JsClass<'js> + 'a> Drop for Borrow<'a, 'js, T> {
fn drop(&mut self) {
unsafe { <T::Mutable as Mutability>::unborrow(self.0) }
}
}
impl<'a, 'js, T: JsClass<'js>> Deref for Borrow<'a, 'js, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { <T::Mutable as Mutability>::deref(self.0) }
}
}
pub struct BorrowMut<'a, 'js, T: JsClass<'js> + 'a>(&'a <T::Mutable as Mutability>::Cell<T>);
impl<'a, 'js, T: JsClass<'js> + 'a> Drop for BorrowMut<'a, 'js, T> {
fn drop(&mut self) {
unsafe { <T::Mutable as Mutability>::unborrow_mut(self.0) }
}
}
impl<'a, 'js, T: JsClass<'js>> Deref for BorrowMut<'a, 'js, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { <T::Mutable as Mutability>::deref(self.0) }
}
}
impl<'a, 'js, T: JsClass<'js>> DerefMut for BorrowMut<'a, 'js, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { <T::Mutable as Mutability>::deref_mut(self.0) }
}
}
pub struct OwnedBorrow<'js, T: JsClass<'js> + 'js>(ManuallyDrop<Class<'js, T>>);
impl<'js, T: JsClass<'js> + 'js> OwnedBorrow<'js, T> {
pub fn from_class(class: Class<'js, T>) -> Self {
Self::try_from_class(class).unwrap()
}
pub fn try_from_class(class: Class<'js, T>) -> Result<Self, BorrowError> {
unsafe {
<T::Mutable as Mutability>::borrow(&class.get_cell().cell)?;
}
Ok(OwnedBorrow(ManuallyDrop::new(class)))
}
pub fn into_inner(mut self) -> Class<'js, T> {
unsafe { <T::Mutable as Mutability>::unborrow(&self.0.get_cell().cell) };
let res = unsafe { ManuallyDrop::take(&mut self.0) };
std::mem::forget(self);
res
}
}
impl<'js, T: JsClass<'js> + 'js> Drop for OwnedBorrow<'js, T> {
fn drop(&mut self) {
unsafe {
<T::Mutable as Mutability>::unborrow(&self.0.get_cell().cell);
ManuallyDrop::drop(&mut self.0)
}
}
}
impl<'js, T: JsClass<'js> + 'js> Deref for OwnedBorrow<'js, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { <T::Mutable as Mutability>::deref(&self.0.get_cell().cell) }
}
}
impl<'js, T: JsClass<'js>> FromJs<'js> for OwnedBorrow<'js, T> {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self, Error> {
let cls = Class::from_js(ctx, value)?;
OwnedBorrow::try_from_class(cls).map_err(Error::ClassBorrow)
}
}
impl<'js, T: JsClass<'js>> IntoJs<'js> for OwnedBorrow<'js, T> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>, Error> {
self.into_inner().into_js(ctx)
}
}
pub struct OwnedBorrowMut<'js, T: JsClass<'js> + 'js>(ManuallyDrop<Class<'js, T>>);
impl<'js, T: JsClass<'js> + 'js> OwnedBorrowMut<'js, T> {
pub fn from_class(class: Class<'js, T>) -> Self {
Self::try_from_class(class).unwrap()
}
pub fn try_from_class(class: Class<'js, T>) -> Result<Self, BorrowError> {
unsafe {
<T::Mutable as Mutability>::borrow_mut(&class.get_cell().cell)?;
}
Ok(OwnedBorrowMut(ManuallyDrop::new(class)))
}
pub fn into_inner(mut self) -> Class<'js, T> {
unsafe { <T::Mutable as Mutability>::unborrow_mut(&self.0.get_cell().cell) };
let res = unsafe { ManuallyDrop::take(&mut self.0) };
std::mem::forget(self);
res
}
}
impl<'js, T: JsClass<'js> + 'js> Drop for OwnedBorrowMut<'js, T> {
fn drop(&mut self) {
unsafe {
<T::Mutable as Mutability>::unborrow_mut(&self.0.get_cell().cell);
ManuallyDrop::drop(&mut self.0)
}
}
}
impl<'js, T: JsClass<'js> + 'js> Deref for OwnedBorrowMut<'js, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { <T::Mutable as Mutability>::deref(&self.0.get_cell().cell) }
}
}
impl<'js, T: JsClass<'js> + 'js> DerefMut for OwnedBorrowMut<'js, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { <T::Mutable as Mutability>::deref_mut(&self.0.get_cell().cell) }
}
}
impl<'js, T: JsClass<'js>> FromJs<'js> for OwnedBorrowMut<'js, T> {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self, Error> {
let cls = Class::from_js(ctx, value)?;
OwnedBorrowMut::try_from_class(cls).map_err(Error::ClassBorrow)
}
}
impl<'js, T: JsClass<'js>> IntoJs<'js> for OwnedBorrowMut<'js, T> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>, Error> {
self.into_inner().into_js(ctx)
}
}