use std::sync::Arc;
use anyhow::{Result, anyhow, bail};
use parking_lot::Mutex;
use super::borrow::{self, BorrowGuard};
use super::bridge::arg;
use super::bytecode::BuiltinId;
use super::value::{CellKind, Value, ValueRef};
pub(super) fn make_cell(kind: CellKind, inner: Value) -> Value {
Value::Cell(kind, Arc::new(Mutex::new(inner)))
}
pub(super) fn cell_method(
kind: CellKind,
slot: &Arc<Mutex<Value>>,
name: BuiltinId,
args: &mut [Value],
) -> Result<Option<Value>> {
if kind.is_shared_pointer() && interior_method(name) {
let inner = slot.lock().clone();
let Value::Cell(inner_kind, inner_slot) = inner else {
bail!("no method `{}` on {}", name.name(), kind_name(kind));
};
return cell_method(inner_kind, &inner_slot, name, args);
}
Ok(Some(match name {
BuiltinId::Clone => Value::Cell(kind, slot.clone()).deep_clone(),
BuiltinId::Borrow | BuiltinId::BorrowMut => {
require(kind, CellKind::RefCell, name)?;
match borrow::acquire(slot, name == BuiltinId::BorrowMut) {
Ok(guard) => borrowed(slot, guard),
Err(failure) => bail!("{}", failure.message()),
}
}
BuiltinId::TryBorrow | BuiltinId::TryBorrowMut => {
require(kind, CellKind::RefCell, name)?;
match borrow::acquire(slot, name == BuiltinId::TryBorrowMut) {
Ok(guard) => Value::ok(borrowed(slot, guard)),
Err(failure) => Value::err(failure.value()),
}
}
BuiltinId::Lock | BuiltinId::TryLock | BuiltinId::BlockingLock => {
if kind == CellKind::TokioMutex {
let guard = Value::Ref(Arc::new(ValueRef::cell_slot(slot.clone())));
if name == BuiltinId::TryLock {
Value::ok(guard)
} else {
guard
}
} else {
require(kind, CellKind::Mutex, name)?;
if name == BuiltinId::BlockingLock {
bail!("no method `blocking_lock` on std Mutex");
}
Value::ok(Value::Ref(Arc::new(ValueRef::cell_slot(slot.clone()))))
}
}
BuiltinId::Get if kind == CellKind::Cell => slot.lock().clone(),
BuiltinId::Set => {
require_interior(kind, name)?;
let new = arg(args, 0)?;
*slot.lock() = new;
Value::Unit
}
BuiltinId::Replace => {
require_interior(kind, name)?;
let new = arg(args, 0)?;
let mut guard = slot.lock();
let old = guard.clone();
*guard = new;
old
}
BuiltinId::Take => {
require_interior(kind, name)?;
let mut guard = slot.lock();
let old = guard.clone();
*guard = old.default_like();
old
}
BuiltinId::GetMut => {
require_interior(kind, name)?;
Value::Ref(Arc::new(ValueRef::cell_slot(slot.clone())))
}
BuiltinId::IntoInner => slot.lock().clone(),
_ => return Ok(None),
}))
}
fn borrowed(slot: &Arc<Mutex<Value>>, guard: Arc<BorrowGuard>) -> Value {
Value::Ref(Arc::new(ValueRef::borrowed_cell_slot(slot.clone(), guard)))
}
fn interior_method(name: BuiltinId) -> bool {
matches!(
name,
BuiltinId::Borrow
| BuiltinId::BorrowMut
| BuiltinId::TryBorrow
| BuiltinId::TryBorrowMut
| BuiltinId::Lock
| BuiltinId::TryLock
| BuiltinId::BlockingLock
| BuiltinId::Get
| BuiltinId::Set
| BuiltinId::Replace
| BuiltinId::Take
| BuiltinId::GetMut
)
}
fn kind_name(kind: CellKind) -> &'static str {
match kind {
CellKind::Rc => "Rc",
CellKind::Arc => "Arc",
CellKind::RefCell => "RefCell",
CellKind::Cell => "Cell",
CellKind::Mutex | CellKind::TokioMutex => "Mutex",
}
}
fn require(kind: CellKind, wanted: CellKind, name: BuiltinId) -> Result<()> {
if kind == wanted {
Ok(())
} else {
Err(anyhow!(
"no method `{}` on {}",
name.name(),
kind_name(kind)
))
}
}
fn require_interior(kind: CellKind, name: BuiltinId) -> Result<()> {
if kind.is_shared_pointer() {
Err(anyhow!(
"no method `{}` on {}",
name.name(),
kind_name(kind)
))
} else {
Ok(())
}
}