use pyo3::{
exceptions::{PyRuntimeError, PyValueError},
intern,
prelude::*,
sync::PyOnceLock,
types::IntoPyDict,
PyTypeInfo,
};
use wasm_runtime_layer::{
backend::{Extern, Ref, Val},
RefType, ValType,
};
use crate::{Engine, ExternRef};
pub trait ToPy {
fn to_py(&self, py: Python) -> Result<Py<PyAny>, PyErr>;
}
impl ToPy for Val<Engine> {
fn to_py(&self, py: Python) -> Result<Py<PyAny>, PyErr> {
#[cfg(feature = "tracing")]
tracing::trace!(ty = ?self.ty(), "Value::to_py");
match self {
Self::I32(v) => Ok(v.into_pyobject(py)?.into_any().unbind()),
Self::I64(v) => i64_to_js_bigint(py, *v).map(Bound::unbind),
Self::F32(v) => Ok(v.into_pyobject(py)?.into_any().unbind()),
Self::F64(v) => Ok(v.into_pyobject(py)?.into_any().unbind()),
Self::V128(_) => Err(PyValueError::new_err(
"v128 values are not supported in the pyodide-webassembly-runtime-layer backend",
)),
Self::FuncRef(None) | Self::ExternRef(None) => Ok(py.None()),
Self::FuncRef(Some(func)) => func.to_py(py),
Self::ExternRef(Some(r#ref)) => r#ref.to_py(py),
}
}
}
impl ToPy for Ref<Engine> {
fn to_py(&self, py: Python) -> Result<Py<PyAny>, PyErr> {
match self {
Self::FuncRef(None) | Self::ExternRef(None) => Ok(py.None()),
Self::FuncRef(Some(func)) => func.to_py(py),
Self::ExternRef(Some(r#ref)) => r#ref.to_py(py),
}
}
}
impl ToPy for Extern<Engine> {
fn to_py(&self, py: Python) -> Result<Py<PyAny>, PyErr> {
#[cfg(feature = "tracing")]
tracing::trace!("Extern::to_py");
match self {
Self::Global(v) => v.to_py(py),
Self::Table(v) => v.to_py(py),
Self::Memory(v) => v.to_py(py),
Self::Func(v) => v.to_py(py),
}
}
}
pub trait ValExt: Sized {
fn from_py_typed(value: Bound<PyAny>, ty: ValType) -> Result<Self, PyErr>;
}
impl ValExt for Val<Engine> {
fn from_py_typed(value: Bound<PyAny>, ty: ValType) -> Result<Self, PyErr> {
match ty {
ValType::I32 => Ok(Self::I32(value.extract()?)),
ValType::I64 => Ok(Self::I64(try_i64_from_js_bigint(value)?)),
ValType::F32 => Ok(Self::F32(value.extract()?)),
ValType::F64 => Ok(Self::F64(value.extract()?)),
ValType::V128 => Err(PyValueError::new_err(
"v128 values are not supported in the pyodide-webassembly-runtime-layer backend",
)),
ValType::ExternRef => {
if value.is_none() {
Ok(Self::ExternRef(None))
} else {
Ok(Self::ExternRef(Some(ExternRef::from_exported_externref(
value,
))))
}
},
ValType::FuncRef => {
if value.is_none() {
Ok(Self::FuncRef(None))
} else {
Err(PyRuntimeError::new_err(
"conversion to a function outside of a module export is not permitted as \
its type signature is unknown",
))
}
},
}
}
}
pub trait ValTypeExt {
fn as_js_descriptor(&self) -> &str;
}
impl ValTypeExt for ValType {
fn as_js_descriptor(&self) -> &str {
match self {
Self::I32 => "i32",
Self::I64 => "i64",
Self::F32 => "f32",
Self::F64 => "f64",
Self::V128 => "v128",
Self::FuncRef => "anyfunc",
Self::ExternRef => "externref",
}
}
}
pub trait RefExt: Sized {
fn from_py_typed(value: Bound<PyAny>, ty: RefType) -> Result<Self, PyErr>;
}
impl RefExt for Ref<Engine> {
fn from_py_typed(value: Bound<PyAny>, ty: RefType) -> Result<Self, PyErr> {
match ty {
RefType::ExternRef => {
if value.is_none() {
Ok(Self::ExternRef(None))
} else {
Ok(Self::ExternRef(Some(ExternRef::from_exported_externref(
value,
))))
}
},
RefType::FuncRef => {
if value.is_none() {
Ok(Self::FuncRef(None))
} else {
Err(PyRuntimeError::new_err(
"conversion to a function outside of a module export is not permitted as \
its type signature is unknown",
))
}
},
}
}
}
pub trait RefTypeExt {
fn as_js_descriptor(&self) -> &str;
}
impl RefTypeExt for RefType {
fn as_js_descriptor(&self) -> &str {
match self {
Self::FuncRef => "anyfunc",
Self::ExternRef => "externref",
}
}
}
fn i64_to_js_bigint(py: Python, v: i64) -> Result<Bound<PyAny>, PyErr> {
fn object_wrapped_bigint(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
static OBJECT_WRAPPED_BIGINT: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
OBJECT_WRAPPED_BIGINT
.get_or_try_init(py, || {
Ok(py
.import(intern!(py, "pyodide"))?
.getattr(intern!(py, "code"))?
.getattr(intern!(py, "run_js"))?
.call1((
"function objectWrappedBigInt(v){ return Object(BigInt(v)); } \
objectWrappedBigInt",
))?
.unbind())
})
.map(|x| x.bind(py))
}
object_wrapped_bigint(py)?.call1((v,))
}
fn try_i64_from_js_bigint(v: Bound<PyAny>) -> Result<i64, PyErr> {
fn js_bigint(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
static JS_BIG_INT: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
JS_BIG_INT.import(py, "js", "BigInt")
}
js_bigint(v.py())?.call1((v,))?.extract()
}
pub fn js_uint8_array_new(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
static JS_UINT8_ARRAY_NEW: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
JS_UINT8_ARRAY_NEW.import(py, "js.Uint8Array", "new")
}
pub fn instanceof(object: &Bound<PyAny>, constructor: &Bound<PyAny>) -> Result<bool, PyErr> {
fn is_instance_of(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
static IS_INSTANCE_OF: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
IS_INSTANCE_OF
.get_or_try_init(py, || {
Ok(py
.import(intern!(py, "pyodide"))?
.getattr(intern!(py, "code"))?
.getattr(intern!(py, "run_js"))?
.call1((
"function isInstanceOf(object, constructor){ return (object instanceof \
constructor); } isInstanceOf",
))?
.unbind())
})
.map(|x| x.bind(py))
}
is_instance_of(object.py())?
.call1((object, constructor))?
.extract()
}
pub fn create_js_object(py: Python) -> Result<Bound<PyAny>, PyErr> {
fn js_object_new(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
static JS_OBJECT_NEW: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
JS_OBJECT_NEW.import(py, "js.Object", "new")
}
js_object_new(py)?.call0()
}
pub fn py_to_js_proxy<T: PyTypeInfo>(object: Bound<T>) -> Result<Bound<PyAny>, PyErr> {
fn to_js(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
static TO_JS: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
TO_JS.import(py, "pyodide.ffi", "to_js")
}
let py = object.py();
to_js(py)?.call(
(object,),
Some(&[(intern!(py, "create_pyproxies"), true)].into_py_dict(py)?),
)
}