diff --git a/Cargo.lock b/Cargo.lock index 55afb99b02d..f58a87f678b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2958,8 +2958,7 @@ dependencies = [ [[package]] name = "pyo3" version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" +source = "git+https://github.com/PyO3/pyo3#08dde9fcc0deb58685858bbc2cfca6e51ffab337" dependencies = [ "libc", "once_cell", @@ -2972,8 +2971,7 @@ dependencies = [ [[package]] name = "pyo3-build-config" version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" +source = "git+https://github.com/PyO3/pyo3#08dde9fcc0deb58685858bbc2cfca6e51ffab337" dependencies = [ "target-lexicon", ] @@ -2981,8 +2979,7 @@ dependencies = [ [[package]] name = "pyo3-ffi" version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" +source = "git+https://github.com/PyO3/pyo3#08dde9fcc0deb58685858bbc2cfca6e51ffab337" dependencies = [ "libc", "pyo3-build-config", @@ -2991,8 +2988,7 @@ dependencies = [ [[package]] name = "pyo3-macros" version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" +source = "git+https://github.com/PyO3/pyo3#08dde9fcc0deb58685858bbc2cfca6e51ffab337" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -3003,12 +2999,10 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" +source = "git+https://github.com/PyO3/pyo3#08dde9fcc0deb58685858bbc2cfca6e51ffab337" dependencies = [ "heck", "proc-macro2", - "pyo3-build-config", "quote", "syn", ] diff --git a/Cargo.toml b/Cargo.toml index eda23858f3c..6b483a230d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,6 +127,7 @@ panic = "abort" [patch.crates-io] parking_lot_core = { git = "https://github.com/youknowone/parking_lot", branch = "rustpython" } +pyo3-ffi = { git = "https://github.com/PyO3/pyo3" } # REDOX START, Uncomment when you want to compile/check with redoxer # REDOX END @@ -278,7 +279,7 @@ pkcs8 = "0.11" proc-macro2 = "1.0.105" psm = "0.1" pymath = { version = "0.2.0", features = ["mul_add", "malachite-bigint", "complex"] } -pyo3 = "0.28" +pyo3 = { git = "https://github.com/PyO3/pyo3" } quote = "1.0.45" radium = "1.1.1" rand = "0.10" diff --git a/Lib/test/test_ctypes/test_python_api.py b/Lib/test/test_ctypes/test_python_api.py index 28abf2ac031..e35cd8917ff 100644 --- a/Lib/test/test_ctypes/test_python_api.py +++ b/Lib/test/test_ctypes/test_python_api.py @@ -7,7 +7,6 @@ class PythonAPITestCase(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON; - requires pythonapi (Python C API) def test_PyBytes_FromStringAndSize(self): PyBytes_FromStringAndSize = pythonapi.PyBytes_FromStringAndSize diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index 70b7fc4b8c5..34179c7fd19 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -21,6 +21,10 @@ rustpython-stdlib = {workspace = true, features = ["threading"] } [dev-dependencies] pyo3 = { workspace = true, features = ["auto-initialize", "abi3"] } +[features] +# Enable PyObject_CallMethodObjArgs variadic function, which is only available on nightly Rust. +nightly = [] + [lints] workspace = true diff --git a/crates/capi/capi_todo.md b/crates/capi/capi_todo.md new file mode 100644 index 00000000000..128de75bb7b --- /dev/null +++ b/crates/capi/capi_todo.md @@ -0,0 +1,92 @@ +# Unimplemented C API functions + +Mapping source: `pyo3-ffi/src/*.rs`, which mirrors the CPython header split used by the C API. + +## `dictobject.h` + +RustPython C API target: `crates/capi/src/dictobject.rs` + +- `PyDict_Contains` +- `PyDict_Copy` +- `PyDict_DelItem` +- `PyDict_Items` +- `PyDict_Keys` +- `PyDict_Merge` +- `PyDict_MergeFromSeq2` +- `PyDict_Update` +- `PyDict_Values` + +## `genericaliasobject.h` + +RustPython C API target: `crates/capi/src/genericaliasobject.rs` (not present yet) + +- `Py_GenericAlias` + +## `import.h` + +RustPython C API target: `crates/capi/src/import.rs` + +- `PyImport_ExecCodeModuleEx` + +## `listobject.h` + +RustPython C API target: `crates/capi/src/listobject.rs` + +- `PyList_AsTuple` +- `PyList_GetSlice` +- `PyList_SetSlice` +- `PyList_Sort` + +## `longobject.h` + +RustPython C API target: `crates/capi/src/longobject.rs` + +- `PyLong_AsUnsignedLongLongMask` + +## `modsupport.h` + +RustPython C API target: `crates/capi/src/modsupport.rs` (not present yet) + +- `PyModule_ExecDef` +- `PyModule_FromDefAndSpec2` + +## `object.h` + +RustPython C API target: `crates/capi/src/object.rs` + +- `PyCallable_Check` +- `PyObject_ClearWeakRefs` +- `PyObject_Dir` +- `PyObject_GenericGetAttr` +- `PyObject_GetOptionalAttr` +- `PyObject_RichCompare` +- `PyType_GetModuleName` + +## `osmodule.h` + +RustPython C API target: `crates/capi/src/osmodule.rs` (not present yet) + +- `PyOS_FSPath` + +## `pyerrors.h` + +RustPython C API target: `crates/capi/src/pyerrors.rs` + +- `PyException_SetContext` +- `PyUnicodeDecodeError_Create` + +## `pystate.h` + +RustPython C API target: `crates/capi/src/pystate.rs` + +- `PyInterpreterState_Get` +- `PyInterpreterState_GetID` + +## `unicodeobject.h` + +RustPython C API target: `crates/capi/src/unicodeobject.rs` + +- `PyUnicode_AsUTF8String` +- `PyUnicode_DecodeFSDefaultAndSize` +- `PyUnicode_EncodeFSDefault` +- `PyUnicode_FromEncodedObject` diff --git a/crates/capi/pyo3-rustpython.config b/crates/capi/pyo3-rustpython.config index fe59e46e895..ce725b9bf46 100644 --- a/crates/capi/pyo3-rustpython.config +++ b/crates/capi/pyo3-rustpython.config @@ -1,4 +1,4 @@ -implementation=CPython +implementation=RustPython version=3.14 shared=true abi3=true diff --git a/crates/capi/src/abstract_.rs b/crates/capi/src/abstract_.rs index 01a3964f728..7c5ccb58faa 100644 --- a/crates/capi/src/abstract_.rs +++ b/crates/capi/src/abstract_.rs @@ -57,6 +57,25 @@ pub unsafe extern "C" fn PyObject_CallNoArgs(callable: *mut PyObject) -> *mut Py with_vm(|vm| unsafe { &*callable }.call((), vm)) } +#[unsafe(no_mangle)] +#[cfg(feature = "nightly")] +pub unsafe extern "C" fn PyObject_CallMethodObjArgs( + receiver: *mut PyObject, + name: *mut PyObject, + mut args: ... +) -> *mut PyObject { + with_vm(|vm| { + let method_name = unsafe { (&*name).try_downcast_ref::(vm)? }; + let callable = unsafe { (&*receiver).get_attr(method_name, vm)? }; + let arguments = core::iter::from_fn(|| unsafe { + core::ptr::NonNull::new(args.next_arg::<*mut PyObject>()) + .map(|obj| obj.as_ref().to_owned()) + }) + .collect::>(); + callable.call(arguments, vm) + }) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn PyObject_Vectorcall( callable: *mut PyObject, @@ -180,3 +199,52 @@ pub unsafe extern "C" fn PyObject_Size(obj: *mut PyObject) -> isize { obj.length(vm) }) } + +#[cfg(test)] +mod tests { + use pyo3::prelude::*; + use pyo3::types::{PyDict, PyString}; + + #[test] + #[cfg(feature = "nightly")] + fn test_call_method0() { + Python::attach(|py| { + let string = PyString::new(py, "Hello, World!"); + assert_eq!( + string.call_method0("upper").unwrap().str().unwrap(), + "HELLO, WORLD!" + ); + }) + } + + #[test] + fn test_call_method1() { + Python::attach(|py| { + let string = PyString::new(py, "Hello, World!"); + assert!( + string + .call_method1("endswith", ("!",)) + .unwrap() + .is_truthy() + .unwrap() + ); + }) + } + + #[test] + fn test_object_set_get_del_item() { + Python::attach(|py| { + let obj = PyDict::new(py).into_any(); + obj.set_item("key", "value").unwrap(); + assert_eq!( + obj.get_item("key") + .unwrap() + .cast_into::() + .unwrap(), + "value" + ); + obj.del_item("key").unwrap(); + assert!(obj.get_item("key").is_err()); + }) + } +} diff --git a/crates/capi/src/abstract_/iter.rs b/crates/capi/src/abstract_/iter.rs index 1ba5bd04d19..fbd1440e0d0 100644 --- a/crates/capi/src/abstract_/iter.rs +++ b/crates/capi/src/abstract_/iter.rs @@ -89,7 +89,7 @@ pub unsafe extern "C" fn PyIter_Send( }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::{PyAnyMethods, PyIterator, PyList, PySendResult}; diff --git a/crates/capi/src/abstract_/mapping.rs b/crates/capi/src/abstract_/mapping.rs index 18b188613dc..54cf175ff7b 100644 --- a/crates/capi/src/abstract_/mapping.rs +++ b/crates/capi/src/abstract_/mapping.rs @@ -37,7 +37,7 @@ pub unsafe extern "C" fn PyMapping_Items(obj: *mut PyObject) -> *mut PyObject { }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::{PyDict, PyMapping, PyMappingMethods, PyTuple}; diff --git a/crates/capi/src/abstract_/number.rs b/crates/capi/src/abstract_/number.rs index c5ec492a73d..912ac677682 100644 --- a/crates/capi/src/abstract_/number.rs +++ b/crates/capi/src/abstract_/number.rs @@ -30,7 +30,7 @@ pub unsafe extern "C" fn PyNumber_Subtract(o1: *mut PyObject, o2: *mut PyObject) with_vm(|vm| vm._sub(unsafe { &*o1 }, unsafe { &*o2 })) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; diff --git a/crates/capi/src/abstract_/sequence.rs b/crates/capi/src/abstract_/sequence.rs index f6022b93e91..d011dfdb8eb 100644 --- a/crates/capi/src/abstract_/sequence.rs +++ b/crates/capi/src/abstract_/sequence.rs @@ -177,7 +177,7 @@ pub unsafe extern "C" fn PySequence_In(obj: *mut PyObject, value: *mut PyObject) unsafe { PySequence_Contains(obj, value) } } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::{PyAnyMethods, PyDict, PyList, PySequence, PySequenceMethods, PyTuple}; diff --git a/crates/capi/src/bytearrayobject.rs b/crates/capi/src/bytearrayobject.rs index 2bc56895ba8..cc9db5dd50e 100644 --- a/crates/capi/src/bytearrayobject.rs +++ b/crates/capi/src/bytearrayobject.rs @@ -71,7 +71,7 @@ pub unsafe extern "C" fn PyByteArray_Resize(bytearray: *mut PyObject, len: isize }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::{PyByteArray, PyBytes}; diff --git a/crates/capi/src/bytesobject.rs b/crates/capi/src/bytesobject.rs index 1fe535efba5..efa802859cd 100644 --- a/crates/capi/src/bytesobject.rs +++ b/crates/capi/src/bytesobject.rs @@ -1,6 +1,5 @@ -use crate::PyObject; use crate::object::define_py_check; -use crate::pystate::with_vm; +use crate::{PyObject, pystate::with_vm}; use core::ffi::c_char; use rustpython_vm::builtins::PyBytes; @@ -46,7 +45,7 @@ pub unsafe extern "C" fn PyBytes_AsString(bytes: *mut PyObject) -> *mut c_char { }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::PyBytes; diff --git a/crates/capi/src/ceval.rs b/crates/capi/src/ceval.rs index be8d9c48b61..24610fe5c9f 100644 --- a/crates/capi/src/ceval.rs +++ b/crates/capi/src/ceval.rs @@ -72,7 +72,7 @@ pub extern "C" fn PyEval_GetBuiltins() -> *mut PyObject { }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::exceptions::PyException; use pyo3::prelude::*; diff --git a/crates/capi/src/complexobject.rs b/crates/capi/src/complexobject.rs index a6b2bb731a0..ad0eaf2c77c 100644 --- a/crates/capi/src/complexobject.rs +++ b/crates/capi/src/complexobject.rs @@ -36,7 +36,7 @@ pub unsafe extern "C" fn PyComplex_ImagAsDouble(obj: *mut PyObject) -> c_double with_vm(|vm| try_to_complex(vm, unsafe { &*obj }).map(|complex| complex.im)) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::PyComplex; diff --git a/crates/capi/src/descrobject.rs b/crates/capi/src/descrobject.rs index 5232634fabb..33b58f87e2d 100644 --- a/crates/capi/src/descrobject.rs +++ b/crates/capi/src/descrobject.rs @@ -11,7 +11,7 @@ pub unsafe extern "C" fn PyDictProxy_New(mapping: *mut PyObject) -> *mut PyObjec }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::{PyDict, PyInt, PyMappingProxy}; diff --git a/crates/capi/src/dictobject.rs b/crates/capi/src/dictobject.rs index dc5dd58485e..7ac18194074 100644 --- a/crates/capi/src/dictobject.rs +++ b/crates/capi/src/dictobject.rs @@ -56,6 +56,43 @@ pub unsafe extern "C" fn PyDict_GetItemRef( }) } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyDict_SetDefaultRef( + dict: *mut PyObject, + key: *mut PyObject, + default_value: *mut PyObject, + result: *mut *mut PyObject, +) -> c_int { + with_vm(|vm| { + let result = NonNull::new(result); + if let Some(result) = result { + unsafe { + result.write(core::ptr::null_mut()); + } + } + let dict = unsafe { &*dict }.try_downcast_ref::(vm)?; + let key = unsafe { &*key }; + + if let Some(value) = dict.inner_getitem_opt(key, vm)? { + if let Some(result) = result { + unsafe { + result.write(value.into_raw().as_ptr()); + } + } + Ok(true) + } else { + let value = unsafe { &*default_value }.to_owned(); + dict.inner_setitem(key, value.clone(), vm)?; + if let Some(result) = result { + unsafe { + result.write(value.into_raw().as_ptr()); + } + } + Ok(false) + } + }) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn PyDict_Size(dict: *mut PyObject) -> isize { with_vm(|vm| { @@ -92,7 +129,7 @@ pub unsafe extern "C" fn PyDict_Next( }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyDict, PyInt}; diff --git a/crates/capi/src/floatobject.rs b/crates/capi/src/floatobject.rs index f1bb078106d..a7063f9c137 100644 --- a/crates/capi/src/floatobject.rs +++ b/crates/capi/src/floatobject.rs @@ -24,7 +24,7 @@ pub unsafe extern "C" fn PyFloat_AsDouble(obj: *mut PyObject) -> c_double { }) } -#[cfg(false)] +#[cfg(test)] mod tests { use core::f64::consts::PI; use pyo3::prelude::*; diff --git a/crates/capi/src/import.rs b/crates/capi/src/import.rs index d380d1f8266..fb2e738c7b5 100644 --- a/crates/capi/src/import.rs +++ b/crates/capi/src/import.rs @@ -1,4 +1,5 @@ use crate::{PyObject, pystate::with_vm}; +use core::ffi::{CStr, c_char}; use rustpython_vm::builtins::PyStr; #[unsafe(no_mangle)] @@ -9,7 +10,20 @@ pub unsafe extern "C" fn PyImport_Import(name: *mut PyObject) -> *mut PyObject { }) } -#[cfg(false)] +#[unsafe(no_mangle)] +pub extern "C" fn PyImport_AddModuleRef(name: *const c_char) -> *mut PyObject { + with_vm(|vm| { + let name = unsafe { CStr::from_ptr(name) } + .to_str() + .expect("Name is not valid UTF-8"); + + // TODO check if module already exists and return it if so, instead of creating a new one + + vm.new_module(name, vm.ctx.new_dict(), None) + }) +} + +#[cfg(test)] mod tests { use pyo3::prelude::*; diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index fb3bc687f4d..0439b01d51e 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(feature = "nightly", feature(c_variadic))] #![allow(clippy::missing_safety_doc)] use crate::pyerrors::init_exception_statics; @@ -23,6 +24,7 @@ pub mod longobject; pub mod methodobject; pub mod moduleobject; pub mod object; +pub mod objimpl; pub mod pycapsule; pub mod pyerrors; pub mod pylifecycle; diff --git a/crates/capi/src/listobject.rs b/crates/capi/src/listobject.rs index 1f70e37b651..3a223b6e589 100644 --- a/crates/capi/src/listobject.rs +++ b/crates/capi/src/listobject.rs @@ -110,7 +110,7 @@ pub unsafe extern "C" fn PyList_Reverse(list: *mut PyObject) -> c_int { }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::exceptions::PyIndexError; use pyo3::prelude::*; diff --git a/crates/capi/src/longobject.rs b/crates/capi/src/longobject.rs index 8c9fe5e1acb..603b6851a1c 100644 --- a/crates/capi/src/longobject.rs +++ b/crates/capi/src/longobject.rs @@ -1,6 +1,5 @@ -use crate::PyObject; use crate::object::define_py_check; -use crate::pystate::with_vm; +use crate::{PyObject, pystate::with_vm}; use core::ffi::{c_long, c_longlong, c_ulong, c_ulonglong}; use rustpython_vm::PyResult; use rustpython_vm::builtins::PyInt; @@ -64,7 +63,7 @@ pub unsafe extern "C" fn PyLong_AsUnsignedLongLong(obj: *mut PyObject) -> c_ulon }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::PyInt; diff --git a/crates/capi/src/methodobject.rs b/crates/capi/src/methodobject.rs index c0a6611a01f..31d87085eac 100644 --- a/crates/capi/src/methodobject.rs +++ b/crates/capi/src/methodobject.rs @@ -306,7 +306,7 @@ pub unsafe extern "C" fn PyCFunction_NewEx( unsafe { PyCMethod_New(ml, slf, module, core::ptr::null_mut()) } } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::exceptions::PyException; use pyo3::ffi::{PyLong_FromLong, PyObject}; diff --git a/crates/capi/src/moduleobject.rs b/crates/capi/src/moduleobject.rs index dd753455406..1f0c5668787 100644 --- a/crates/capi/src/moduleobject.rs +++ b/crates/capi/src/moduleobject.rs @@ -1,11 +1,92 @@ use crate::PyObject; +use crate::methodobject::PyMethodDef; use crate::object::define_py_check; use crate::pystate::with_vm; use rustpython_vm::builtins::{PyModule, PyStr}; +use rustpython_vm::AsObject; +use std::ffi::{CStr, c_char, c_int, c_void}; define_py_check!(fn PyModule_Check, types.module_type); define_py_check!(exact fn PyModule_CheckExact, types.module_type); +const PY_MOD_CREATE: c_int = 1; +const PY_MOD_EXEC: c_int = 2; +const PY_MOD_GIL: c_int = 4; + +#[repr(C)] +pub struct PyModuleDef { + m_base: [u8; 40], + m_name: *const c_char, + m_doc: *const c_char, + m_size: isize, + m_methods: *mut PyMethodDef, + m_slots: *mut PyModuleDef_Slot, + m_traverse: *mut c_void, + m_clear: *mut c_void, + m_free: *mut c_void, +} + +#[repr(C)] +pub struct PyModuleDef_Slot { + id: c_int, + value: PyModuleDef_SlotValue, +} + +union PyModuleDef_SlotValue { + exec_module: extern "C" fn(*mut PyObject) -> c_int, + gil_used: usize, +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyModuleDef_Init(def: *mut PyModuleDef) -> *mut PyObject { + with_vm(|vm| { + let name = unsafe { CStr::from_ptr((&*def).m_name) } + .to_str() + .expect("Module name is not valid UTF-8"); + let doc = unsafe { CStr::from_ptr((*def).m_doc) } + .to_str() + .expect("Module doc is not valid UTF-8"); + let dict = vm.ctx.new_dict(); + + let module = vm.new_module(name, dict, Some(vm.ctx.new_str(doc))); + + let mut slot_ptr = unsafe { (*def).m_slots }; + while let slot = unsafe { &*slot_ptr } + && slot.id != 0 + { + match slot.id { + PY_MOD_CREATE => { + return Err(vm.new_import_error( + "RustPython does not support modules that define a create slot", + vm.ctx.new_str(name), + )); + } + PY_MOD_EXEC => { + let exec_module = unsafe { slot.value.exec_module }; + if exec_module(module.as_object().as_raw().cast_mut()) != 0 { + return Err(vm + .take_raised_exception() + .expect("Module exec function failed without setting an exception")); + } + } + PY_MOD_GIL => { + if unsafe { slot.value.gil_used == 0 } { + return Err(vm.new_import_error( + "RustPython does not support modules that require the GIL", + vm.ctx.new_str(name), + )); + } + } + _ => todo!("Got unknown PyModuleDef_Slot with id {}", slot.id), + } + + slot_ptr = unsafe { slot_ptr.add(1) }; + } + + Ok(module) + }) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn PyModule_GetNameObject(module: *mut PyObject) -> *mut PyObject { with_vm(|vm| { @@ -40,3 +121,53 @@ pub unsafe extern "C" fn PyModule_NewObject(name: *mut PyObject) -> *mut PyObjec Ok(vm.new_module(name, vm.ctx.new_dict(), None)) }) } + +#[cfg(test)] +mod tests { + use super::PyModuleDef; + use core::mem::offset_of; + use pyo3::prelude::*; + + #[test] + fn test_create_module() { + const { + assert!( + offset_of!(PyModuleDef, m_name) == size_of::(), + "PyModuleDef::m_base was not the expected size" + ); + }; + + #[pymodule] + mod my_extension { + use pyo3::prelude::*; + + #[pymodule_export] + const PI: f64 = std::f64::consts::PI; + + #[pyfunction] // Inline definition of a pyfunction, also made available to Python + fn triple(x: usize) -> usize { + x * 3 + } + } + + Python::attach(|py| { + let module = unsafe { + Borrowed::from_ptr(py, my_extension::__pyo3_init()).cast_unchecked::() + }; + assert_eq!(module.name().unwrap(), "my_extension"); + + module.getattr("PI").unwrap().extract::().unwrap(); + + assert_eq!( + module + .getattr("triple") + .unwrap() + .call1((10,)) + .unwrap() + .extract::() + .unwrap(), + 30 + ); + }) + } +} diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index 9866d9041f9..fbcc855a654 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -1,9 +1,12 @@ -use crate::PyObject; -use crate::pystate::with_vm; -use core::ffi::{CStr, c_char, c_int, c_uint, c_ulong}; +use crate::methodobject::{PyMethodDef, build_method_def}; +use crate::{PyObject, pystate::with_vm}; +use core::ffi::{CStr, c_char, c_int, c_uint, c_ulong, c_void}; use core::ptr::NonNull; -use rustpython_vm::builtins::{PyStr, PyType}; -use rustpython_vm::{AsObject, Py}; +use rustpython_vm::builtins::{PyDict, PyStr, PyTuple, PyType}; +use rustpython_vm::convert::IntoObject; +use rustpython_vm::function::{FuncArgs, PyMethodFlags}; +use rustpython_vm::types::{PyTypeFlags, PyTypeSlots, SlotAccessor}; +use rustpython_vm::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; pub type PyTypeObject = Py; @@ -93,6 +96,258 @@ pub unsafe extern "C" fn PyType_GetFullyQualifiedName(ptr: *const PyTypeObject) }) } +#[unsafe(no_mangle)] +pub extern "C" fn PyType_GetSlot(ty: *const PyTypeObject, slot: c_int) -> *mut c_void { + with_vm(|_vm| { + let ty = unsafe { &*ty }; + let slot: u8 = slot + .try_into() + .expect("slot number out of range for SlotAccessor"); + let slot_accessor: SlotAccessor = slot + .try_into() + .expect("invalid slot number for SlotAccessor"); + + match slot_accessor { + SlotAccessor::TpNew => { + extern "C" fn newfunc_wrapper( + subtype: *mut PyTypeObject, + args: *mut PyObject, + kwargs: *mut PyObject, + ) -> *mut PyObject { + with_vm(|vm| { + let subtype = unsafe { &*subtype }; + let mut func_args = FuncArgs::default(); + + if let Some(args_obj) = unsafe { args.as_ref() } { + let tuple = args_obj.try_downcast_ref::(vm)?; + func_args + .args + .extend(tuple.iter().map(|arg| arg.to_owned())); + } + + if let Some(kwargs_obj) = unsafe { kwargs.as_ref() } { + let kwargs = kwargs_obj.try_downcast_ref::(vm)?; + for (key, value) in kwargs.items_vec() { + let key = key.try_downcast::(vm)?; + func_args + .kwargs + .insert(key.to_string_lossy().into_owned(), value); + } + } + + subtype + .slots + .new + .load() + .expect("tp_new slot function pointer is null")( + subtype.to_owned(), + func_args, + vm, + ) + }) + } + + if let Some(vtable) = ty.get_type_data::() { + vtable.new_func.map(|newfunc| newfunc as *mut c_void) + } else { + ty.slots.new.load().map(|_| newfunc_wrapper as *mut c_void) + } + } + _ => { + todo!("Slot {slot_accessor:?} for {ty:?} is not yet implemented in PyType_GetSlot") + } + } + .unwrap_or_default() + }) +} + +#[repr(C)] +pub struct PyType_Slot { + slot: c_int, + pfunc: *mut c_void, +} + +#[repr(C)] +pub struct PyType_Spec { + name: *const c_char, + basicsize: c_int, + itemsize: c_int, + flags: c_uint, + slots: *mut PyType_Slot, +} + +#[repr(C)] +pub struct PyGetSetDef { + name: *const c_char, + get: extern "C" fn(*mut PyObject, usize) -> *mut PyObject, + set: Option c_int>, + doc: *const c_char, + closure: usize, +} + +#[derive(Default)] +struct TypeVTable { + new_func: Option, +} + +type newfunc = unsafe extern "C" fn( + ty: *mut PyTypeObject, + args: *mut PyObject, + kwargs: *mut PyObject, +) -> *mut PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyType_FromSpec(spec: *mut PyType_Spec) -> *mut PyObject { + with_vm(|vm| -> PyResult { + let spec = unsafe { &*spec }; + let class_name = unsafe { + CStr::from_ptr(spec.name) + .to_str() + .expect("type name must be valid UTF-8") + }; + let mut base = vm.ctx.types.object_type; + let mut slots = PyTypeSlots::heap_default(); + + slots.basicsize = spec.basicsize as _; + slots.itemsize = spec.itemsize as _; + slots.flags = PyTypeFlags::from_bits(spec.flags as u64).expect("invalid flags value"); + + let mut attributes: &[PyGetSetDef] = &[]; + let mut methods: &[PyMethodDef] = &[]; + let mut vtable = TypeVTable::default(); + let mut slot_ptr = spec.slots; + while let slot = unsafe { &*slot_ptr } + && slot.slot != 0 + { + let accessor = SlotAccessor::try_from(slot.slot as u8) + .expect("invalid slot number in PyType_Spec"); + + match accessor { + SlotAccessor::TpDealloc => { + slots.del.store(Some(|ty, _vm| { + todo!("tp_dealloc is not yet implemented in PyType_FromSpec for {ty:?}") + })); + } + SlotAccessor::TpBase => base = unsafe { &*slot.pfunc.cast::() }, + SlotAccessor::TpGetset => { + let start = slot.pfunc.cast::(); + let mut end = start; + while unsafe { !(*end).name.is_null() } { + end = unsafe { end.add(1) } + } + attributes = unsafe { + core::slice::from_raw_parts(start, end.offset_from(start) as usize) + }; + } + SlotAccessor::TpMethods => { + let start = slot.pfunc.cast::(); + let mut end = start; + while unsafe { !(*end).ml_name.is_null() } { + end = unsafe { end.add(1) } + } + methods = unsafe { + core::slice::from_raw_parts(start, end.offset_from(start) as usize) + }; + } + SlotAccessor::TpNew => { + vtable.new_func = Some(unsafe { core::mem::transmute(slot.pfunc) }); + slots.new.store(Some(|ty, args, vm| { + let new_func = ty.get_type_data::().unwrap().new_func.unwrap(); + let kwargs = vm.ctx.new_dict(); + for (name, value) in &args.kwargs { + kwargs.set_item(&*vm.ctx.new_str(name.clone()), value.clone(), vm)?; + } + let args = vm.ctx.new_tuple(args.args); + let result = unsafe { + new_func( + (&*ty) as *const _ as *mut _, + args.as_object().as_raw().cast_mut(), + kwargs.as_object().as_raw().cast_mut(), + ) + }; + + unsafe { Ok(PyObjectRef::from_raw(NonNull::new(result).unwrap())) } + })); + } + SlotAccessor::TpDoc => { + let doc = unsafe { + CStr::from_ptr(slot.pfunc.cast::()) + .to_str() + .expect("tp_doc must be a valid UTF-8 string") + }; + slots.doc = Some(doc); + } + _ => todo!("Slot {accessor:?} is not yet supported in PyType_FromSpec"), + } + + slot_ptr = unsafe { slot_ptr.add(1) }; + } + + let class = vm.ctx.new_class(None, class_name, base.to_owned(), slots); + class.init_type_data(vtable).unwrap(); + for attribute in attributes { + let name = unsafe { + CStr::from_ptr(attribute.name) + .to_str() + .expect("attribute name must be valid UTF-8") + }; + let closure = attribute.closure; + let getter = attribute.get; + let getset = if let Some(setter) = attribute.set { + todo!(); + unsafe { + vm.ctx.new_getset( + name, + &class, + |obj: PyObjectRef, vm: &VirtualMachine| {}, + |obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine| {}, + ) + } + } else { + let class = unsafe { &*((&*class) as *const _) }; + vm.ctx.new_readonly_getset( + name, + &class, + move |obj: PyObjectRef, vm: &VirtualMachine| { + let result = getter(obj.as_raw().cast_mut(), closure); + unsafe { + PyObjectRef::from_raw( + NonNull::new(result).expect("TODO handle error from c function"), + ) + } + }, + ) + }; + class + .attributes + .write() + .insert(vm.ctx.intern_str(name), getset.into_object()); + } + for method in methods { + let class_static = unsafe { &*((&*class) as *const _) }; + let name = unsafe { + CStr::from_ptr(method.ml_name) + .to_str() + .expect("method name must be valid UTF-8") + }; + let is_static = PyMethodFlags::from_bits_retain(method.ml_flags as _) + .contains(PyMethodFlags::STATIC); + let method = build_method_def(vm, method, !is_static)?.build_method(class_static, vm); + class + .attributes + .write() + .insert(vm.ctx.intern_str(name), method.into()); + } + Ok(class.into()) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyType_Freeze(_ty: *mut PyTypeObject) -> c_int { + // TODO: Implement immutable type freezing semantics. + 0 +} + #[unsafe(no_mangle)] pub extern "C" fn Py_GetConstantBorrowed(constant_id: c_uint) -> *mut PyObject { with_vm(|vm| { @@ -201,3 +456,212 @@ pub unsafe extern "C" fn PyObject_IsTrue(obj: *mut PyObject) -> c_int { obj.to_owned().is_true(vm) }) } + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_GenericGetDict( + obj: *mut PyObject, + _context: *mut c_void, +) -> *mut PyObject { + with_vm(|vm| { + let obj = unsafe { &*obj }; + obj.get_attr("__dict__", vm) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_GenericSetDict( + obj: *mut PyObject, + value: *mut PyObject, + _context: *mut c_void, +) -> c_int { + with_vm(|vm| { + let obj = unsafe { &*obj }; + let value = unsafe { &*value }.to_owned(); + obj.set_attr("__dict__", value, vm) + }) +} + +#[cfg(test)] +mod tests { + use pyo3::prelude::*; + use pyo3::types::{PyBool, PyDict, PyInt, PyNone, PyString, PyType}; + + #[test] + fn test_is_truthy() { + Python::attach(|py| { + assert!(!py.None().is_truthy(py).unwrap()); + }) + } + + #[test] + fn test_is_none() { + Python::attach(|py| { + assert!(py.None().is_none(py)); + }) + } + + #[test] + fn test_bool() { + Python::attach(|py| { + assert!(PyBool::new(py, true).is_truthy().unwrap()); + assert!(!PyBool::new(py, false).is_truthy().unwrap()); + }) + } + + #[test] + fn test_type_name() { + Python::attach(|py| { + let string = PyString::new(py, "Hello, World!"); + assert_eq!(string.get_type().name().unwrap().to_str().unwrap(), "str"); + }) + } + + #[test] + fn test_static_type_pointers() { + Python::attach(|py| { + assert!(py.None().bind(py).is_instance_of::()); + assert!(PyBool::new(py, true).is_instance_of::()); + }) + } + + #[test] + fn test_repr() { + Python::attach(|py| { + let module = py.import("sys").unwrap(); + assert_eq!(module.repr().unwrap(), ""); + }) + } + + #[test] + fn test_obj_to_str() { + Python::attach(|py| { + let number = PyInt::new(py, 42); + assert_eq!(number.str().unwrap(), "42"); + }) + } + + #[test] + fn test_get_attr() { + Python::attach(|py| { + let sys = py.import("sys").unwrap(); + let implementation = sys + .getattr("implementation") + .unwrap() + .getattr("name") + .unwrap() + .str() + .unwrap(); + + assert_eq!(implementation, "rustpython"); + }) + } + + #[test] + fn test_generic_get_dict() { + Python::attach(|py| { + let globals = PyDict::new(py); + py.run(c"class MyClass: ...", None, Some(&globals)).unwrap(); + let my_class = globals.get_item("MyClass").unwrap().unwrap(); + let instance = my_class.call0().unwrap(); + instance.setattr("foo", 42).unwrap(); + let dict = unsafe { + Bound::from_owned_ptr_or_err( + py, + pyo3::ffi::PyObject_GenericGetDict(instance.as_ptr(), std::ptr::null_mut()), + ) + } + .unwrap(); + assert!(dict.get_item("foo").is_ok()); + }) + } + + #[test] + fn test_rust_class() { + #[pyclass] + struct MyClass { + #[pyo3(get)] + num: i32, + } + + #[pymethods] + impl MyClass { + #[new] + fn new(value: i32) -> Self { + MyClass { num: value } + } + + fn method1(&self) -> PyResult { + Ok(self.num + 10) + } + + fn method2(&self, a: i32) -> PyResult { + Ok(self.num + a) + } + + #[staticmethod] + fn static_method1(a: i32, b: i32) -> PyResult { + Ok(a + b) + } + + #[staticmethod] + fn static_method2() -> PyResult { + Ok(0) + } + + #[classmethod] + fn cls_method(cls: &Bound<'_, PyType>) -> PyResult { + assert!(cls.is_subclass_of::()?); + Ok(10) + } + } + + 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 + ); + + 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 + ); + }); + } +} diff --git a/crates/capi/src/objimpl.rs b/crates/capi/src/objimpl.rs new file mode 100644 index 00000000000..4242aa9f2f1 --- /dev/null +++ b/crates/capi/src/objimpl.rs @@ -0,0 +1,13 @@ +use crate::PyObject; +use crate::pystate::with_vm; +use rustpython_vm::gc_state; + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_GC_UnTrack(op: *mut PyObject) { + with_vm(|_vm| { + let obj = unsafe { &*op }; + if obj.is_gc_tracked() { + unsafe { gc_state::gc_state().untrack_object(obj.into()) }; + } + }) +} diff --git a/crates/capi/src/pycapsule.rs b/crates/capi/src/pycapsule.rs index 7a5d599c851..0e1b55b617d 100644 --- a/crates/capi/src/pycapsule.rs +++ b/crates/capi/src/pycapsule.rs @@ -142,7 +142,7 @@ fn checked_capsule<'a>( Ok(capsule) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::PyCapsule; diff --git a/crates/capi/src/pyerrors.rs b/crates/capi/src/pyerrors.rs index b767b7c4090..d346a25417e 100644 --- a/crates/capi/src/pyerrors.rs +++ b/crates/capi/src/pyerrors.rs @@ -299,9 +299,29 @@ pub unsafe extern "C" fn PyException_GetContext(exc: *mut PyObject) -> *mut PyOb }) } +#[unsafe(no_mangle)] +pub extern "C" fn PyException_SetCause(exc: *mut PyObject, cause: *mut PyObject) { + with_vm(|vm| { + let exc = unsafe { &*exc }; + let cause = unsafe { cause.as_ref() }.map(|obj| obj.to_owned()); + exc.set_attr("__cause__", vm.unwrap_or_none(cause), vm) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyException_SetTraceback(exc: *mut PyObject, tb: *mut PyObject) -> c_int { + with_vm(|vm| { + let exc = unsafe { &*exc }; + let traceback = unsafe { tb.as_ref() }.map(|obj| obj.to_owned()); + exc.set_attr("__traceback__", vm.unwrap_or_none(traceback), vm) + }) +} + #[cfg(test)] mod tests { - use pyo3::exceptions::PyTypeError; + use pyo3::PyTypeInfo; + use pyo3::create_exception; + use pyo3::exceptions::{PyException, PyTypeError}; use pyo3::prelude::*; #[test] @@ -309,7 +329,7 @@ mod tests { Python::attach(|py| { PyTypeError::new_err(py.None()).restore(py); assert!(PyErr::occurred(py)); - assert!(unsafe { !pyo3::ffi::PyErr_GetRaisedException().is_null() }); + assert!(PyErr::take(py).is_some()); assert!(!PyErr::occurred(py)); }) } @@ -321,4 +341,19 @@ mod tests { assert!(err.is_instance_of::(py)); }) } + + #[test] + fn test_new_exception_type() { + create_exception!(my_module, MyError, PyException, "Some description."); + + Python::attach(|py| { + let exc = MyError::new_err("This is a new exception"); + assert!(exc.is_instance_of::(py)); + let exc_type = MyError::type_object(py); + assert_eq!( + exc_type.fully_qualified_name().unwrap(), + "my_module.MyError" + ); + }) + } } diff --git a/crates/capi/src/pylifecycle.rs b/crates/capi/src/pylifecycle.rs index 6760b2822a3..52feaf2c719 100644 --- a/crates/capi/src/pylifecycle.rs +++ b/crates/capi/src/pylifecycle.rs @@ -1,10 +1,12 @@ use crate::get_main_interpreter; use crate::pyerrors::init_exception_statics; use crate::pystate::ensure_thread_has_vm_attached; -use core::ffi::c_int; +use core::ffi::{c_char, c_int}; +use rustpython_vm::version::{MAJOR, MICRO, MINOR, VERSION_HEX}; use rustpython_vm::vm::thread::ThreadedVirtualMachine; use rustpython_vm::{Context, Interpreter}; -use std::sync::Mutex; +use std::ffi::c_ulong; +use std::sync::{LazyLock, Mutex}; pub(crate) static MAIN_INTERP: Mutex> = Mutex::new(None); @@ -16,6 +18,9 @@ pub(crate) fn request_vm_from_interpreter() -> ThreadedVirtualMachine { .enter(|vm| vm.new_thread()) } +#[unsafe(no_mangle)] +pub static Py_Version: c_ulong = VERSION_HEX as c_ulong; + #[unsafe(no_mangle)] pub extern "C" fn Py_IsInitialized() -> c_int { get_main_interpreter().is_some() as c_int @@ -52,3 +57,24 @@ pub extern "C" fn Py_FinalizeEx() -> c_int { pub extern "C" fn Py_IsFinalizing() -> c_int { 0 } + +#[unsafe(no_mangle)] +pub extern "C" fn Py_GetVersion() -> *const c_char { + static VERSION: LazyLock = LazyLock::new(|| format!("{MAJOR}.{MINOR}.{MICRO}")); + VERSION.as_str().as_ptr() as *const c_char +} + +#[cfg(test)] +mod tests { + use pyo3::prelude::*; + + #[test] + fn test_get_version() { + Python::attach(|py| { + let version = py.version_info(); + assert!(version >= (3, 14)); + }); + + assert!(unsafe { pyo3::ffi::Py_Version } >= 0x030d0000); + } +} diff --git a/crates/capi/src/refcount.rs b/crates/capi/src/refcount.rs index 917dfeec2b9..8ff369249a1 100644 --- a/crates/capi/src/refcount.rs +++ b/crates/capi/src/refcount.rs @@ -1,4 +1,4 @@ -use crate::PyObject; +use crate::{PyObject, pystate::with_vm}; use core::ptr::NonNull; use rustpython_vm::PyObjectRef; @@ -13,3 +13,32 @@ pub unsafe extern "C" fn _Py_IncRef(op: *mut PyObject) { // Don't drop the owned value, as we just want to increment the refcount. core::mem::forget(unsafe { (*op).to_owned() }); } + +#[unsafe(no_mangle)] +pub extern "C" fn Py_NewRef(op: *mut PyObject) -> *mut PyObject { + with_vm(|_vm| unsafe { (*op).to_owned() }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_REFCNT(op: *mut PyObject) -> isize { + with_vm(|_vm| unsafe { &*op }.strong_count()) +} + +#[cfg(test)] +mod tests { + use pyo3::prelude::*; + use pyo3::types::PyInt; + use pyo3::{PyTypeInfo, ffi}; + + #[test] + fn test_refcount() { + Python::attach(|py| unsafe { + let obj = PyInt::type_object(py); + let ref_count = ffi::Py_REFCNT(obj.as_ptr()); + let obj_clone = obj.clone(); + assert_eq!(ffi::Py_REFCNT(obj.as_ptr()), ref_count + 1); + drop(obj_clone); + assert_eq!(ffi::Py_REFCNT(obj.as_ptr()), ref_count); + }); + } +} diff --git a/crates/capi/src/setobject.rs b/crates/capi/src/setobject.rs index cc479371b27..1036bb1473a 100644 --- a/crates/capi/src/setobject.rs +++ b/crates/capi/src/setobject.rs @@ -116,7 +116,7 @@ pub unsafe extern "C" fn PySet_Size(anyset: *mut PyObject) -> isize { }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::{PyFrozenSet, PyInt, PySet}; diff --git a/crates/capi/src/sliceobject.rs b/crates/capi/src/sliceobject.rs index 2a625fab523..fb588181531 100644 --- a/crates/capi/src/sliceobject.rs +++ b/crates/capi/src/sliceobject.rs @@ -72,7 +72,7 @@ pub unsafe extern "C" fn PySlice_AdjustIndices( slice_len as isize } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::{PySlice, PySliceMethods}; diff --git a/crates/capi/src/tupleobject.rs b/crates/capi/src/tupleobject.rs index 985141f6d4c..0aaeb5290b2 100644 --- a/crates/capi/src/tupleobject.rs +++ b/crates/capi/src/tupleobject.rs @@ -41,6 +41,19 @@ pub unsafe extern "C" fn PyTuple_FromArray( }) } +#[unsafe(no_mangle)] +#[cfg(feature = "nightly")] +pub unsafe extern "C" fn PyTuple_Pack(len: isize, mut args: ...) -> *mut PyObject { + with_vm(|vm| { + let items = + core::iter::repeat_with(|| unsafe { (&*args.next_arg::<*mut PyObject>()).to_owned() }) + .take(len as usize) + .collect::>(); + + vm.new_tuple(items) + }) +} + #[unsafe(no_mangle)] pub extern "C" fn PyTuple_SetItem( _tuple: *mut PyObject, @@ -90,7 +103,7 @@ pub unsafe extern "C" fn PyTuple_GetSlice( }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::PyTuple; diff --git a/crates/capi/src/unicodeobject.rs b/crates/capi/src/unicodeobject.rs index 76e0fb0df58..e604cf8db9e 100644 --- a/crates/capi/src/unicodeobject.rs +++ b/crates/capi/src/unicodeobject.rs @@ -1,6 +1,5 @@ -use crate::PyObject; use crate::object::define_py_check; -use crate::pystate::with_vm; +use crate::{PyObject, pystate::with_vm}; use core::ffi::{CStr, c_char, c_int}; use core::ptr::NonNull; use core::slice; @@ -129,7 +128,7 @@ pub unsafe extern "C" fn PyUnicode_EqualToUTF8AndSize( }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::intern; use pyo3::prelude::*; diff --git a/crates/capi/src/warnings.rs b/crates/capi/src/warnings.rs index 22ffd6ab939..4966cd60d6d 100644 --- a/crates/capi/src/warnings.rs +++ b/crates/capi/src/warnings.rs @@ -92,7 +92,7 @@ pub unsafe extern "C" fn PyErr_WarnExplicit( }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::exceptions::{PyRuntimeWarning, PyUserWarning}; use pyo3::prelude::*; diff --git a/crates/capi/src/weakrefobject.rs b/crates/capi/src/weakrefobject.rs index 706de84b53d..de095e5e594 100644 --- a/crates/capi/src/weakrefobject.rs +++ b/crates/capi/src/weakrefobject.rs @@ -65,7 +65,7 @@ pub unsafe extern "C" fn PyWeakref_NewRef( }) } -#[cfg(false)] +#[cfg(test)] mod tests { use pyo3::prelude::*; use pyo3::types::PyAnyMethods; diff --git a/crates/vm/src/types/slot_defs.rs b/crates/vm/src/types/slot_defs.rs index 956bc6fa4d1..e5aa061cb41 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}; 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) - Reserved, not used in RustPython diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 7775c34e053..8518f871cda 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -2024,7 +2024,7 @@ impl VirtualMachine { } /// Push a new exc_info slot (for generator/coroutine resume). - pub(crate) fn push_exception(&self, exc: Option) { + pub fn push_exception(&self, exc: Option) { self.exceptions.borrow_mut().stack.push(exc); #[cfg(feature = "threading")] thread::update_thread_exception(self.topmost_exception());