diff --git a/Cargo.lock b/Cargo.lock index 039c0dc1b13..9ab6b5cc10f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3187,9 +3187,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b" +checksum = "700d18fa267b73b9b521fd7e13580e2f446916f176cacee1ab63fcc8191f1655" dependencies = [ "libc", "once_cell", @@ -3201,18 +3201,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9" +checksum = "7b3fc0c4d08f6bb10e71fe39dfb9e2f59c6eb6854e22ec8092f50c69a4499adb" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327" +checksum = "dfc0b8e19df29aad7086cf977bb0c2a2f143e30567eb113e9cf72b62ca698330" dependencies = [ "libc", "pyo3-build-config", @@ -3220,9 +3220,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c" +checksum = "6100e8a4b5eba53afaa5ed078364851a0b2499c553a44c31026b929049b49dc6" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -3232,9 +3232,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952" +checksum = "6143877a16e82b5a727b7127ff4cd86858a24a28d745d72f43e6f227c7b1bdb3" dependencies = [ "heck", "proc-macro2", diff --git a/crates/capi/src/descrobject.rs b/crates/capi/src/descrobject.rs index c600f2d0194..1e53b72e0ce 100644 --- a/crates/capi/src/descrobject.rs +++ b/crates/capi/src/descrobject.rs @@ -29,6 +29,17 @@ pub struct PyGetSetDef { } impl PyGetSetDef { + pub(crate) fn iter<'a>(mut defs: *const Self) -> impl Iterator { + core::iter::from_fn(move || { + let def = unsafe { &*defs }; + if def.name.is_null() { + None + } else { + defs = unsafe { defs.add(1) }; + Some(def) + } + }) + } pub(crate) fn build( &self, ty: &'static Py, @@ -114,6 +125,18 @@ pub struct PyMemberDef { } impl PyMemberDef { + pub(crate) fn iter<'a>(mut defs: *const Self) -> impl Iterator { + core::iter::from_fn(move || { + let def = unsafe { &*defs }; + if def.name.is_null() { + None + } else { + defs = unsafe { defs.add(1) }; + Some(def) + } + }) + } + pub(crate) fn build( &self, ty: &Py, diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index 22d658354c5..17a5af5ee21 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -41,6 +41,7 @@ pub mod pystrcmp; pub mod refcount; pub mod setobject; pub mod sliceobject; +pub mod slots; pub mod traceback; pub mod tupleobject; pub mod unicodeobject; diff --git a/crates/capi/src/methodobject.rs b/crates/capi/src/methodobject.rs index ac3620a6763..9868db36cac 100644 --- a/crates/capi/src/methodobject.rs +++ b/crates/capi/src/methodobject.rs @@ -4,6 +4,7 @@ use crate::object::define_py_check; use crate::pystate::with_vm; use crate::util::{CStrExt, FfiPtrExt}; use core::ffi::{c_char, c_int}; +use core::fmt::Debug; use rustpython_vm::function::{FuncArgs, HeapMethodDef, ItemDoc, PosArgs, PyMethodFlags}; use rustpython_vm::{AsObject, PyObjectRef, PyRef, PyResult, VirtualMachine}; @@ -11,6 +12,7 @@ define_py_check!(fn PyCFunction_Check, types.builtin_function_or_method_type); define_py_check!(exact fn PyCFunction_CheckExact, types.builtin_function_or_method_type); #[repr(C)] +#[derive(Debug)] pub struct PyMethodDef { pub ml_name: *const c_char, pub ml_meth: PyMethodPointer, @@ -18,6 +20,20 @@ pub struct PyMethodDef { pub ml_doc: *const c_char, } +impl PyMethodDef { + pub(crate) fn iter<'a>(mut methods: *const Self) -> impl Iterator { + core::iter::from_fn(move || { + let def = unsafe { &*methods }; + if def.ml_name.is_null() { + None + } else { + methods = unsafe { methods.add(1) }; + Some(def) + } + }) + } +} + #[repr(C)] #[derive(Copy, Clone)] #[allow(non_snake_case)] @@ -41,6 +57,12 @@ pub union PyMethodPointer { ) -> *mut PyObject, } +impl Debug for PyMethodPointer { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + unsafe { self.PyCFunction.fmt(f) } + } +} + pub(crate) fn build_method_def( vm: &VirtualMachine, ml: &PyMethodDef, diff --git a/crates/capi/src/object/pytype.rs b/crates/capi/src/object/pytype.rs index d0ae62b1bc3..d1383ec4cb5 100644 --- a/crates/capi/src/object/pytype.rs +++ b/crates/capi/src/object/pytype.rs @@ -1,8 +1,14 @@ +use crate::descrobject::{PyGetSetDef, PyMemberDef}; +use crate::methodobject::{PyMethodDef, build_method_def}; use crate::object::define_py_check; use crate::pystate::with_vm; +use crate::slots::{PySlot, PySlotKind, PySlotType}; +use crate::util::CStrExt; use crate::util::FfiPtrExt; -use core::ffi::{c_int, c_ulong}; +use core::ffi::{c_char, c_int, c_ulong, c_void}; use rustpython_vm::builtins::{PyStr, PyType}; +use rustpython_vm::function::{ItemDoc, PyMethodFlags}; +use rustpython_vm::types::{PyAtomicTypeFlags, PyTypeFlags, PyTypeSlots, SlotAccessor}; use rustpython_vm::{AsObject, Py, PyObject}; pub type PyTypeObject = Py; @@ -10,6 +16,31 @@ pub type PyTypeObject = Py; define_py_check!(fn PyType_Check, types.type_type); define_py_check!(exact fn PyType_CheckExact, types.type_type); +#[repr(C)] +pub struct PyType_Slot { + pub slot: c_int, + pub pfunc: *mut c_void, +} + +impl PyType_Slot { + pub(crate) fn iter<'a>(mut slots: *const Self) -> impl Iterator { + core::iter::from_fn(move || { + let slot = unsafe { &*slots }; + if slot.slot == 0 { + None + } else { + slots = unsafe { slots.add(1) }; + Some(slot) + } + }) + } + + fn as_slot_accessor(&self) -> Option { + let slot_id: u8 = self.slot.try_into().ok()?; + slot_id.try_into().ok() + } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn Py_TYPE(op: *mut PyObject) -> *const PyTypeObject { unsafe { op.assume_borrowed() }.class() @@ -71,10 +102,192 @@ pub unsafe extern "C" fn PyType_GetFullyQualifiedName(ptr: *mut PyTypeObject) -> }) } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyType_GetSlot(_ty: *const PyTypeObject, _slot: c_int) -> *mut c_void { + core::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyType_FromSlots(slots: *const PySlot) -> *mut PyObject { + with_vm(|vm| { + let mut name = None; + let mut base = None; + let mut methods = Vec::new(); + let mut type_slots: PyTypeSlots = Default::default(); + let attrs = Default::default(); + let mut getsets = Vec::new(); + let mut members = Vec::new(); + + for slot in PySlot::iter(slots) { + match slot.as_kind(vm)? { + kind @ PySlotKind::Type(type_slot) => { + match type_slot { + PySlotType::Name(value) => name = Some(value), + PySlotType::Flags(value) => { + let flags = PyTypeFlags::from_bits(value).ok_or_else(|| { + vm.new_value_error(format!( + "Invalid type flags: {value:#x} for PyType_FromSlots" + )) + })?; + type_slots.flags = PyAtomicTypeFlags::new(flags); + } + PySlotType::BasicSize(size) | PySlotType::ExtraBasicSize(size) => { + if size != 0 { + return Err(vm.new_not_implemented_error( + "PyType_FromSlots with non-zero size is not yet supported", + )); + } + } + PySlotType::Slots { value, .. } => { + for slot in PyType_Slot::iter(value) { + let accessor = slot.as_slot_accessor().ok_or_else(|| { + vm.new_value_error(format!( + "Invalid slot id: {} for PyType_FromSlots", + slot.slot + )) + })?; + match accessor { + SlotAccessor::TpDoc => { + let doc = unsafe { + slot.pfunc.cast::().try_as_str_opt(vm)? + } + .map_or( + ItemDoc::NONE, + |doc| { + let text: &'static str = + Box::leak(doc.to_owned().into_boxed_str()); + ItemDoc::static_text(text) + }, + ); + type_slots.doc = doc; + } + SlotAccessor::TpNew => { + type_slots.new.store(Some(|ty, _args, vm| { + Err(vm.new_not_implemented_error(format!("tp_new is not yet implemented in PyType_FromSlots for {ty:?}"))) + })); + } + SlotAccessor::TpBase => { + base = unsafe { Some(&*slot.pfunc.cast::()) } + } + SlotAccessor::TpDealloc => { + //TODO + } + SlotAccessor::TpMethods => { + for def in PyMethodDef::iter(slot.pfunc.cast()) { + let name = unsafe { def.ml_name.try_as_str(vm)? }; + let is_static = + PyMethodFlags::from_bits_retain(def.ml_flags as _) + .contains(PyMethodFlags::STATIC); + let method = build_method_def(vm, def, !is_static)?; + methods.push((name, method)); + } + } + SlotAccessor::TpGetset => { + getsets.extend(PyGetSetDef::iter(slot.pfunc.cast())); + } + SlotAccessor::TpMembers => { + members.extend(PyMemberDef::iter(slot.pfunc.cast())); + } + slot => { + return Err(vm.new_not_implemented_error(format!( + "PyType_FromSlots with PyType_Slot {slot:?} not implemented yet" + ))); + } + } + } + } + _ => { + return Err(vm.new_not_implemented_error(format!( + "PyType_FromSlots with slot {kind:?} not implemented yet" + ))); + } + } + } + PySlotKind::Module(_) => { + return Err( + vm.new_system_error("Got module slot while type slots are expected") + ); + } + PySlotKind::Unknown { .. } => {} + } + } + + let bases = if let Some(base) = base { + vec![base.to_owned()] + } else { + vec![vm.ctx.types.object_type.to_owned()] + }; + + let Some(name) = name else { + return Err(vm.new_system_error("PyType_FromSlots requires a name slot")); + }; + + let metaclass = vm.ctx.types.type_type.to_owned(); + let class = PyType::new_heap(name, bases, attrs, type_slots, metaclass, &vm.ctx).map_err( + |msg| vm.new_system_error(format!("Failed to create type from slots: {msg}")), + )?; + + let attrs = &class.attributes; + let class_static = unsafe { &*((&*class) as *const _) }; + for (name, method) in methods { + attrs.insert( + vm.ctx.intern_str(name), + method.build_method(class_static, vm).into(), + ); + } + for getset in getsets { + let name = unsafe { getset.name.try_as_str(vm)? }; + attrs.insert( + vm.ctx.intern_str(name), + getset.build(class_static, vm)?.into(), + ); + } + for member in members { + let name = unsafe { member.name.try_as_str(vm)? }; + attrs.insert( + vm.ctx.intern_str(name), + member.build(class_static, vm)?.into(), + ); + } + + Ok(class) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_GetTypeData( + obj: *mut PyObject, + cls: *mut PyTypeObject, +) -> *mut c_void { + let cls = unsafe { cls.assume_borrowed() }; + let base_basicsize = cls.base.deref().map_or(0, |base| base.slots.basicsize); + let own_basicsize = cls.slots.basicsize.saturating_sub(base_basicsize); + + if own_basicsize == 0 { + obj.cast() + } else { + todo!("PyObject_GetTypeData for non-zero sized types is not yet implemented") + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_GetTypeData_DuringGC( + obj: *mut PyObject, + cls: *mut PyTypeObject, +) -> *mut c_void { + unsafe { PyObject_GetTypeData(obj, cls) } +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyType_Freeze(_ty: *mut PyTypeObject) -> c_int { + 0 +} + #[cfg(test)] mod tests { + use pyo3::IntoPyObjectExt; use pyo3::prelude::*; - use pyo3::types::{PyInt, PyString, PyTypeMethods}; + use pyo3::types::{PyDict, PyInt, PyList, PyString, PyType, PyTypeMethods}; #[test] fn type_name() { @@ -93,4 +306,173 @@ mod tests { ); }) } + + #[test] + #[ignore] + fn rust_class() { + #[pyclass] + struct MyClass { + #[pyo3(get)] + num: i32, + } + + #[pymethods] + impl MyClass { + #[new] + fn new(value: i32) -> Self { + Self { num: value } + } + + fn method1(&self) -> i32 { + self.num + 10 + } + + fn method2(&self, a: i32) -> i32 { + self.num + a + } + } + + Python::attach(|py| { + let obj = Bound::new(py, MyClass { num: 3 }).unwrap(); + + let globals = PyDict::new(py); + globals.set_item("instance", &obj).unwrap(); + py.run(c"assert instance.num == 3", Some(&globals), None) + .unwrap(); + + assert_eq!( + obj.call_method1("method1", ()) + .unwrap() + .extract::() + .unwrap(), + 13 + ); + + assert_eq!( + obj.call_method1("method2", (5,)) + .unwrap() + .extract::() + .unwrap(), + 8 + ); + }); + } + + #[test] + #[ignore] + fn rust_class_with_member() { + #[pyclass(frozen)] + struct MyClass { + #[pyo3(get)] + value: Py, + } + + Python::attach(|py| { + let obj = Bound::new( + py, + MyClass { + value: 1.into_bound_py_any(py).unwrap().unbind(), + }, + ) + .unwrap(); + + let globals = PyDict::new(py); + globals.set_item("instance", &obj).unwrap(); + py.run(c"assert instance.value is None", Some(&globals), None) + .unwrap(); + }); + } + + #[test] + #[ignore] + fn zero_sized_class() { + #[pyclass(frozen)] + struct MyEmptyClass {} + + #[pymethods] + impl MyEmptyClass { + #[new] + fn new() -> Self { + Self {} + } + + #[staticmethod] + fn static_method1(a: i32, b: i32) -> i32 { + a + b + } + + #[staticmethod] + fn static_method2() -> i32 { + 0 + } + + #[classmethod] + fn cls_method(cls: &Bound<'_, PyType>) -> PyResult { + assert!(cls.is_subclass_of::()?); + Ok(10) + } + } + + Python::attach(|py| { + let obj = Bound::new(py, MyEmptyClass {}).unwrap(); + + assert_eq!( + obj.call_method1("static_method1", (5, 8)) + .unwrap() + .extract::() + .unwrap(), + 13 + ); + + assert_eq!( + obj.call_method1("static_method2", ()) + .unwrap() + .extract::() + .unwrap(), + 0 + ); + + assert_eq!( + obj.call_method1("cls_method", ()) + .unwrap() + .extract::() + .unwrap(), + 10 + ); + }); + } + + #[test] + #[ignore] + fn subclass_with_property() { + #[pyclass(frozen, extends=PyList)] + struct MyList {} + + #[pymethods] + impl MyList { + #[new] + fn new() -> Self { + Self {} + } + + #[getter] + fn name(&self) -> &'static str { + "Some list" + } + } + + Python::attach(|py| { + let obj = Bound::new(py, MyList {}).unwrap(); + + assert!(obj.is_instance_of::()); + + assert_eq!( + obj.getattr("name") + .unwrap() + .cast_into::() + .unwrap(), + "Some list" + ); + }); + } } diff --git a/crates/capi/src/slots.rs b/crates/capi/src/slots.rs new file mode 100644 index 00000000000..73161c16869 --- /dev/null +++ b/crates/capi/src/slots.rs @@ -0,0 +1,202 @@ +use crate::PyObject; +use crate::methodobject::PyMethodDef; +use crate::object::PyType_Slot; +use crate::util::CStrExt; +use core::ffi::{c_char, c_int, c_void}; +use core::slice::from_ref; +use rustpython_vm::builtins::{PyModule, PyType}; +use rustpython_vm::{Py, PyResult, VirtualMachine}; + +#[repr(C)] +pub struct PySlot { + pub sl_id: u16, + pub sl_flags: u16, + _reserved: u32, + pub value: PySlotValue, +} + +impl PySlot { + const SLOT_OPTIONAL: u16 = 0x0001; + const SLOT_STATIC: u16 = 0x0002; + const SLOT_INTPTR: u16 = 0x0004; + + pub(crate) fn iter<'a>(mut slots: *const Self) -> impl Iterator { + core::iter::from_fn(move || { + let slot = unsafe { &*slots }; + if slot.sl_id == 0 { + None + } else { + slots = unsafe { slots.add(1) }; + Some(slot) + } + }) + } + + #[must_use] + pub fn is_optional(&self) -> bool { + self.sl_flags & Self::SLOT_OPTIONAL != 0 + } + + #[must_use] + pub fn is_static(&self) -> bool { + self.sl_flags & Self::SLOT_STATIC != 0 + } + + #[must_use] + pub fn is_intptr(&self) -> bool { + self.sl_flags & Self::SLOT_INTPTR != 0 + } + + pub(crate) fn as_kind(&self, vm: &VirtualMachine) -> PyResult> { + let value_ptr = unsafe { self.value.sl_ptr }; + let is_static = self.is_static(); + let kind = match self.sl_id { + 48 => PySlotKind::Type(PySlotType::Base(unsafe { &*value_ptr.cast() })), + 49 => PySlotKind::Type(PySlotType::Bases(unsafe { from_ref(&*value_ptr.cast()) })), + 84 => { + let create = unsafe { + core::mem::transmute::< + unsafe extern "C" fn(), + unsafe extern "C" fn( + spec: *mut PyObject, + def: *mut c_void, + ) -> *mut PyObject, + >(self.value.sl_func) + }; + PySlotKind::Module(PySlotModule::Create(create)) + } + 85 => { + let exec = unsafe { + core::mem::transmute::< + unsafe extern "C" fn(), + unsafe extern "C" fn(*mut PyObject) -> i32, + >(self.value.sl_func) + }; + PySlotKind::Module(PySlotModule::Exec(exec)) + } + 86 => PySlotKind::Module(PySlotModule::MultipleInterpreters(value_ptr)), + 87 => PySlotKind::Module(PySlotModule::Gil { + gil_used: !value_ptr.is_null(), + }), + // 92 => Py_slot_subslots + 93 => PySlotKind::Type(PySlotType::Slots { + value: value_ptr.cast(), + is_static, + }), + 95 => PySlotKind::Type(PySlotType::Name(unsafe { + value_ptr.cast::().try_as_str(vm)? + })), + 96 => PySlotKind::Type(PySlotType::BasicSize(unsafe { self.value.sl_size })), + 97 => PySlotKind::Type(PySlotType::ExtraBasicSize(unsafe { self.value.sl_size })), + 99 => PySlotKind::Type(PySlotType::Flags(unsafe { self.value.sl_uint64 })), + 100 => PySlotKind::Module(PySlotModule::Name(unsafe { + value_ptr.cast::().try_as_str(vm)? + })), + 101 => PySlotKind::Module(PySlotModule::Doc(unsafe { + value_ptr.cast::().try_as_str(vm)? + })), + // 102 => Py_mod_state_size + 103 => PySlotKind::Module(PySlotModule::Methods(unsafe { &*value_ptr.cast() })), + // 104 => Py_mod_state_traverse + // 105 => Py_mod_state_clear + // 106 => Py_mod_state_free + 107 => PySlotKind::Type(PySlotType::Metaclass(unsafe { &*value_ptr.cast() })), + 108 => PySlotKind::Type(PySlotType::Module(unsafe { &*value_ptr.cast() })), + 109 => PySlotKind::Module(PySlotModule::Abi(unsafe { *value_ptr.cast() })), + // 110 => Py_mod_token + id => { + if self.is_optional() { + PySlotKind::Unknown { + id, + value: value_ptr, + is_static, + } + } else { + return Err(vm.new_system_error(format!("unsupported required slot: {id}"))); + } + } + }; + Ok(kind) + } +} + +#[repr(C)] +pub union PySlotValue { + pub sl_ptr: *mut c_void, + pub sl_func: unsafe extern "C" fn(), + pub sl_size: isize, + pub sl_int64: i64, + pub sl_uint64: u64, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct PyABIInfo { + pub abiinfo_major_version: u8, + pub abiinfo_minor_version: u8, + pub flags: u16, + pub build_version: u32, + pub abi_version: u32, +} + +impl PyABIInfo { + #[allow(dead_code)] + #[must_use] + pub(crate) fn is_supported(&self) -> bool { + const PY_ABIINFO_STABLE: u16 = 0x0001; + const PY_ABIINFO_FREETHREADED: u16 = 0x0004; + + if self.abiinfo_major_version != 1 || self.abiinfo_minor_version != 0 { + return false; + } + + // Only accept abi3t + if self.flags & PY_ABIINFO_STABLE == 0 || self.flags & PY_ABIINFO_FREETHREADED == 0 { + return false; + } + + true + } +} + +#[allow(dead_code)] +#[derive(Debug, Copy, Clone)] +pub(crate) enum PySlotModule<'a> { + Create(unsafe extern "C" fn(spec: *mut PyObject, def: *mut c_void) -> *mut PyObject), + Exec(unsafe extern "C" fn(*mut PyObject) -> c_int), + Name(&'a str), + Doc(&'a str), + Methods(&'a PyMethodDef), + Abi(PyABIInfo), + MultipleInterpreters(*mut c_void), + Gil { gil_used: bool }, +} + +#[allow(dead_code)] +#[derive(Debug, Copy, Clone)] +pub(crate) enum PySlotType<'a> { + Base(&'a Py), + Bases(&'a [Py]), + Slots { + value: *mut PyType_Slot, + is_static: bool, + }, + Name(&'a str), + Flags(u64), + BasicSize(isize), + ExtraBasicSize(isize), + Metaclass(&'a Py), + Module(&'a Py), +} + +#[allow(dead_code)] +#[derive(Debug, Copy, Clone)] +pub(crate) enum PySlotKind<'a> { + Module(PySlotModule<'a>), + Type(PySlotType<'a>), + Unknown { + id: u16, + value: *mut c_void, + is_static: bool, + }, +} diff --git a/crates/vm/src/types/slot_defs.rs b/crates/vm/src/types/slot_defs.rs index 2b1fccfd786..14b41af0aa0 100644 --- a/crates/vm/src/types/slot_defs.rs +++ b/crates/vm/src/types/slot_defs.rs @@ -4,6 +4,7 @@ use super::{PyComparisonOp, PyTypeSlots, fn_addr}; use crate::builtins::descriptor::SlotFunc; +use num_enum::TryFromPrimitive; /// Slot operation type /// @@ -68,7 +69,7 @@ pub struct SlotDef { /// /// Values match CPython's Py_* slot IDs from typeslots.h. /// Unused slots are included for value reservation. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)] #[repr(u8)] pub enum SlotAccessor { // Buffer protocol (1-2)