[oden] The big lifetime removal

It turns out that rust can't really reason about the relationship
between the runtime lifetime and the context lifetime in a way that is
actually usable. This removes the lifetime stuff in favor of reference
counting the runtime itself, via a block that we embed in the
pointer. This, I think, it the least worst option here.
This commit is contained in:
John Doty 2023-06-19 08:28:26 -07:00
parent 898b1fe129
commit 9f808cea31
10 changed files with 269 additions and 312 deletions

View file

@ -2,7 +2,6 @@ use crate::{AtomRef, ContextRef, Error, Result, Runtime, RustFunction};
use oden_js_sys as sys;
use std::ffi::CStr;
use std::fmt;
use std::marker;
use std::ops::{Deref, DerefMut};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@ -52,29 +51,13 @@ impl From<i32> for ValueType {
/// you will want to create and manipulate `Value`, but you will occasionally
/// receive `ValueRef` in calls that originate from JavaScript
/// code. (`ValueRef` is to `Value` as `str` is to `String`, if that helps.)
pub struct ValueRef<'r> {
pub struct ValueRef {
pub(crate) val: sys::JSValue,
_phantom: marker::PhantomData<&'r Runtime>,
}
// pub type Callback<'r, 'ctx> = fn(
// ctx: &'ctx Context<'r>,
// this_val: &ValueRef<'r, 'ctx>,
// argc: usize,
// argvc: &[&ValueRef<'r, 'ctx>],
// ) -> Value<'r, 'ctx>;
impl<'r> ValueRef<'r> {
/// Wrap a value in a specific context.
///
/// **WARNING**: The specified value *must* be associated with at least
/// the runtime of the specified context, if not the context itself. This
/// function makes no attempt to validate this.
pub fn from_raw(val: sys::JSValue, _ctx: &ContextRef<'r>) -> Self {
ValueRef {
val,
_phantom: marker::PhantomData,
}
impl ValueRef {
pub fn from_raw(val: sys::JSValue) -> Self {
ValueRef { val }
}
/// Get the type of JavaScript value that this is.
@ -137,7 +120,7 @@ impl<'r> ValueRef<'r> {
self.value_type() == ValueType::Int
}
pub fn to_i32(&self, ctx: &ContextRef<'r>) -> Result<'r, i32> {
pub fn to_i32(&self, ctx: &ContextRef) -> Result<i32> {
unsafe {
let mut res: i32 = 0;
let ret = sys::JS_ToInt32(ctx.ctx, &mut res, self.val);
@ -149,7 +132,7 @@ impl<'r> ValueRef<'r> {
}
}
pub fn to_u32(&self, ctx: &ContextRef<'r>) -> Result<'r, u32> {
pub fn to_u32(&self, ctx: &ContextRef) -> Result<u32> {
unsafe {
let mut res: u32 = 0;
let ret = sys::JS_ToUint32(ctx.ctx, &mut res, self.val);
@ -161,7 +144,7 @@ impl<'r> ValueRef<'r> {
}
}
pub fn to_i64(&self, ctx: &ContextRef<'r>) -> Result<'r, i64> {
pub fn to_i64(&self, ctx: &ContextRef) -> Result<i64> {
unsafe {
let mut res: i64 = 0;
let ret = sys::JS_ToInt64(ctx.ctx, &mut res, self.val);
@ -180,7 +163,7 @@ impl<'r> ValueRef<'r> {
self.value_type() == ValueType::Bool
}
pub fn to_bool(&self) -> Result<'r, bool> {
pub fn to_bool(&self) -> Result<bool> {
if self.value_type() == ValueType::Bool {
Ok(unsafe { self.val.u.int32 } > 0)
} else {
@ -201,7 +184,7 @@ impl<'r> ValueRef<'r> {
self.value_type() == ValueType::Float64
}
pub fn to_float64(&self, ctx: &ContextRef<'r>) -> Result<'r, f64> {
pub fn to_float64(&self, ctx: &ContextRef) -> Result<f64> {
unsafe {
let mut res: f64 = 0.0;
let ret = sys::JS_ToFloat64(ctx.ctx, &mut res, self.val);
@ -216,7 +199,7 @@ impl<'r> ValueRef<'r> {
/// Take a reference to this value so that it outlives this current reference.
///
/// (This is a very cheap operation: at most it increments a reference count.)
pub fn dup(&self, ctx: &ContextRef<'r>) -> Value<'r> {
pub fn dup(&self, ctx: &ContextRef) -> Value {
unsafe {
sys::JS_DupValue(ctx.ctx, self.val);
}
@ -229,7 +212,7 @@ impl<'r> ValueRef<'r> {
///
/// JavsScript exceptions are are returned as values where `value_type`
/// is `ValueType::Exception`.
pub fn eval_function(&self, ctx: &ContextRef<'r>) -> Result<'r, Value<'r>> {
pub fn eval_function(&self, ctx: &ContextRef) -> Result<Value> {
match self.value_type() {
ValueType::Module | ValueType::FunctionBytecode => (),
found => {
@ -249,25 +232,16 @@ impl<'r> ValueRef<'r> {
Ok(Value::from_raw(result, ctx))
}
pub fn get_property(&self, ctx: &ContextRef<'r>, prop: &str) -> Result<'r, Value<'r>> {
pub fn get_property(&self, ctx: &ContextRef, prop: &str) -> Result<Value> {
let atom = ctx.new_atom(prop)?;
self.get_property_atom(ctx, &atom)
}
pub fn get_property_atom(
&self,
ctx: &ContextRef<'r>,
prop: &AtomRef<'r>,
) -> Result<'r, Value<'r>> {
pub fn get_property_atom(&self, ctx: &ContextRef, prop: &AtomRef) -> Result<Value> {
ctx.check_exception(unsafe { sys::JS_GetProperty(ctx.ctx, self.val, prop.atom) })
}
pub fn set_property(
&mut self,
ctx: &ContextRef<'r>,
prop: &str,
val: &ValueRef,
) -> Result<'r, ()> {
pub fn set_property(&mut self, ctx: &ContextRef, prop: &str, val: &ValueRef) -> Result<()> {
// TODO: Consume API
let atom = ctx.new_atom(prop)?;
self.set_property_atom(ctx, &atom, val)
@ -275,10 +249,10 @@ impl<'r> ValueRef<'r> {
pub fn set_property_atom(
&mut self,
ctx: &ContextRef<'r>,
prop: &AtomRef<'r>,
ctx: &ContextRef,
prop: &AtomRef,
val: &ValueRef,
) -> Result<'r, ()> {
) -> Result<()> {
unsafe {
sys::JS_DupValue(ctx.ctx, val.val);
let result = sys::JS_SetProperty(ctx.ctx, self.val, prop.atom, val.val);
@ -292,29 +266,24 @@ impl<'r> ValueRef<'r> {
pub fn set_fn<F>(
&mut self,
ctx: &ContextRef<'r>,
ctx: &ContextRef,
prop: &str,
func: impl RustFunction<'r, F>,
) -> Result<'r, ()> {
let vr: Value<'r> = ctx.new_fn(func)?;
func: impl RustFunction<F>,
) -> Result<()> {
let vr: Value = ctx.new_fn(func)?;
self.set_property(ctx, prop, &vr)
}
pub fn set_dynamic_method<F>(
&mut self,
ctx: &ContextRef<'r>,
prop: &str,
func: F,
) -> Result<'r, ()>
pub fn set_dynamic_method<F>(&mut self, ctx: &ContextRef, prop: &str, func: F) -> Result<()>
where
F: Fn(&ContextRef<'r>, &ValueRef<'r>, &[&ValueRef<'r>]) -> Result<'r, Value<'r>>,
F: Fn(&ContextRef, &ValueRef, &[&ValueRef]) -> Result<Value>,
{
let vr: Value<'r> = ctx.new_dynamic_fn(func)?;
let vr: Value = ctx.new_dynamic_fn(func)?;
self.set_property(ctx, prop, &vr)
}
/// Convert this value into a string representation of the same value.
pub fn to_string(&self, ctx: &ContextRef<'r>) -> Result<'r, String> {
pub fn to_string(&self, ctx: &ContextRef) -> Result<String> {
// SAFETY: ctx's life is bound by the lifetime of our Context, and
// sys::JS_ToCStringLen2 will return non-null, unless we're out of
// memory.
@ -342,7 +311,7 @@ impl<'r> ValueRef<'r> {
}
}
impl<'ctx> fmt::Debug for ValueRef<'ctx> {
impl<'ctx> fmt::Debug for ValueRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Value")
.field("v", &self.val)
@ -352,50 +321,48 @@ impl<'ctx> fmt::Debug for ValueRef<'ctx> {
/// An owned Value from JavaScript. Unlike ValueRef, this type stands in for
/// values that you construct in Rust code; otherwise they are identical.
pub struct Value<'r> {
value: ValueRef<'r>,
rt: *mut sys::JSRuntime,
_phantom: marker::PhantomData<&'r Runtime>,
pub struct Value {
value: ValueRef,
rt: Runtime,
}
impl<'r> Value<'r> {
impl Value {
/// Take ownership of a value in a specific context.
///
/// **WARNING**: The specified value *must* be associated with at least
/// the runtime of the specified context, if not the context itself. This
/// function makes no attempt to validate this.
pub(crate) fn from_raw(val: sys::JSValue, ctx: &ContextRef<'r>) -> Self {
pub(crate) fn from_raw(val: sys::JSValue, ctx: &ContextRef) -> Self {
Value {
value: ValueRef::from_raw(val, ctx),
rt: unsafe { sys::JS_GetRuntime(ctx.ctx) },
_phantom: marker::PhantomData,
value: ValueRef::from_raw(val),
rt: Runtime::from_raw(unsafe { sys::JS_GetRuntime(ctx.ctx) }),
}
}
}
impl<'r> Deref for Value<'r> {
type Target = ValueRef<'r>;
impl Deref for Value {
type Target = ValueRef;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<'r> DerefMut for Value<'r> {
impl DerefMut for Value {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
impl<'r> Drop for Value<'r> {
impl Drop for Value {
fn drop(&mut self) {
unsafe {
sys::JS_FreeValueRT(self.rt, self.val);
sys::JS_FreeValueRT(self.rt.rt, self.val);
}
}
}
impl<'r> fmt::Debug for Value<'r> {
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.value.fmt(f)
}
@ -409,7 +376,7 @@ mod tests {
#[test]
fn value_type() {
let rt = Runtime::new();
let mut ctx = Context::new(&rt);
let mut ctx = Context::new(rt);
ctx.add_intrinsic_bigfloat();
ctx.add_intrinsic_bigdecimal();
@ -436,36 +403,3 @@ mod tests {
}
}
}
/// NOTE: We can happily create and return values, like this:
/// ```
/// use oden_js::*;
/// fn my_test<'a>(ctx: &'a Context<'a>) -> Value<'a> {
/// ctx.new_i32(123).unwrap()
/// }
/// ```
///
/// But our lifetime bounds on values keep them from outliving their runtimes:
///
/// ```compile_fail
/// use oden_js::*;
/// fn my_test<'r>() -> oden_js::Value<'r> {
/// let rt = Runtime::new();
/// let ctx = Context::new(&rt);
///
/// ctx.new_i32(123).unwrap()
/// }
/// ```
///
/// Contexts are fundamentally transient, though:
///
/// ```
/// use oden_js::*;
/// fn my_test<'r>(runtime: &'r Runtime) -> sheetland_js::Value<'r> {
/// let ctx = Context::new(runtime);
///
/// ctx.new_i32(123).unwrap()
/// }
/// ```
#[cfg(doctest)]
pub struct ValuesObeyLifetimes {}