diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index 9866d9041f9..5e601d3817d 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -1,9 +1,11 @@ use crate::PyObject; use crate::pystate::with_vm; -use core::ffi::{CStr, c_char, c_int, c_uint, c_ulong}; +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::{PyStr, PyType, object_generic_set_dict, object_get_dict}; +use rustpython_vm::bytecode::ComparisonOperator; +use rustpython_vm::function::PySetterValue; +use rustpython_vm::{AsObject, Py, PyPayload}; pub type PyTypeObject = Py; @@ -76,6 +78,11 @@ pub unsafe extern "C" fn PyType_GetQualName(ptr: *const PyTypeObject) -> *mut Py with_vm(|vm| unsafe { &*ptr }.__qualname__(vm)) } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyType_GetModuleName(ptr: *const PyTypeObject) -> *mut PyObject { + with_vm(|vm| unsafe { &*ptr }.__module__(vm)) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn PyType_GetFullyQualifiedName(ptr: *const PyTypeObject) -> *mut PyObject { with_vm(|vm| { @@ -142,6 +149,29 @@ pub unsafe extern "C" fn PyObject_GetAttrString( }) } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_GetOptionalAttr( + obj: *mut PyObject, + name: *mut PyObject, + result: *mut *mut PyObject, +) -> c_int { + with_vm(|vm| { + unsafe { + *result = core::ptr::null_mut(); + } + let obj = unsafe { &*obj }; + let name = unsafe { &*name }.try_downcast_ref::(vm)?; + if let Some(attr) = vm.get_attribute_opt(obj.to_owned(), name)? { + unsafe { + *result = attr.into_raw().as_ptr(); + } + Ok(true) + } else { + Ok(false) + } + }) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn PyObject_SetAttrString( obj: *mut PyObject, @@ -172,6 +202,30 @@ pub unsafe extern "C" fn PyObject_SetAttr( }) } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_HasAttrWithError( + obj: *mut PyObject, + attr_name: *mut PyObject, +) -> c_int { + with_vm(|vm| { + let obj = unsafe { &*obj }; + let name = unsafe { &*attr_name }.try_downcast_ref::(vm)?; + obj.has_attr(name, vm) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_GenericGetAttr( + obj: *mut PyObject, + name: *mut PyObject, +) -> *mut PyObject { + with_vm(|vm| { + let obj = unsafe { &*obj }; + let name = unsafe { &*name }.try_downcast_ref::(vm)?; + obj.generic_getattr(name, vm) + }) +} + #[unsafe(no_mangle)] pub extern "C" fn PyObject_Repr(obj: *mut PyObject) -> *mut PyObject { with_vm(|vm| { @@ -194,6 +248,48 @@ pub extern "C" fn PyObject_Str(obj: *mut PyObject) -> *mut PyObject { }) } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_RichCompare( + left: *mut PyObject, + right: *mut PyObject, + op: c_int, +) -> *mut PyObject { + with_vm(|vm| { + let op = match op { + 0 => ComparisonOperator::Less, + 1 => ComparisonOperator::LessOrEqual, + 2 => ComparisonOperator::Equal, + 3 => ComparisonOperator::NotEqual, + 4 => ComparisonOperator::Greater, + 5 => ComparisonOperator::GreaterOrEqual, + _ => return Err(vm.new_system_error("invalid comparison operator")), + }; + let left = unsafe { &*left }; + let right = unsafe { &*right }; + left.to_owned() + .rich_compare(right.to_owned(), op.into(), vm) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyCallable_Check(obj: *mut PyObject) -> c_int { + with_vm(|_vm| unsafe { obj.as_ref().is_some_and(PyObject::is_callable) }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_ClearWeakRefs(obj: *mut PyObject) { + with_vm(|_vm| unsafe { &*obj }.clear_weak_refs()) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_Dir(obj: *mut PyObject) -> *mut PyObject { + with_vm(|vm| { + unsafe { obj.as_ref() } + .map_or_else(|| vm.dir(None), |obj| obj.to_owned().dir(vm)) + .map(|list| list.into_ref(&vm.ctx)) + }) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn PyObject_IsTrue(obj: *mut PyObject) -> c_int { with_vm(|vm| { @@ -201,3 +297,173 @@ pub unsafe extern "C" fn PyObject_IsTrue(obj: *mut PyObject) -> c_int { obj.to_owned().is_true(vm) }) } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_GenericGetDict( + obj: *mut PyObject, + _context: *mut c_void, +) -> *mut PyObject { + with_vm(|vm| { + let obj = unsafe { &*obj }; + object_get_dict(obj.to_owned(), vm) + }) +} + +#[unsafe(no_mangle)] +pub unsafe 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 = match NonNull::new(value) { + Some(value) => PySetterValue::Assign(unsafe { value.as_ref() }.to_owned()), + None => PySetterValue::Delete, + }; + object_generic_set_dict(obj.to_owned(), value, vm) + }) +} + +#[cfg(false)] +mod tests { + use pyo3::class::basic::CompareOp; + use pyo3::prelude::*; + use pyo3::types::{PyBool, PyDict, PyInt, PyString, PyTypeMethods}; + + #[test] + fn is_truthy() { + Python::attach(|py| { + assert!(!py.None().is_truthy(py).unwrap()); + }) + } + + #[test] + fn is_none() { + Python::attach(|py| { + assert!(py.None().is_none(py)); + }) + } + + #[test] + fn bool() { + Python::attach(|py| { + assert!(PyBool::new(py, true).is_truthy().unwrap()); + assert!(!PyBool::new(py, false).is_truthy().unwrap()); + }) + } + + #[test] + fn 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 repr() { + Python::attach(|py| { + let module = py.import("sys").unwrap(); + assert_eq!(module.repr().unwrap(), ""); + }) + } + + #[test] + fn obj_to_str() { + Python::attach(|py| { + let number = PyInt::new(py, 42); + assert_eq!(number.str().unwrap(), "42"); + }) + } + + #[test] + fn 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 callable_check() { + Python::attach(|py| { + let int_type = py.get_type::(); + assert!(int_type.is_callable()); + assert!(!PyInt::new(py, 42).is_callable()); + }) + } + + #[test] + fn object_dir() { + Python::attach(|py| { + assert!(PyInt::new(py, 42).dir().unwrap().len() > 0); + }) + } + + #[test] + fn get_optional_attr() { + Python::attach(|py| { + let number = PyInt::new(py, 42); + assert!(number.getattr_opt("real").unwrap().is_some()); + assert!( + number + .getattr_opt("attribute_that_should_not_exist") + .unwrap() + .is_none() + ); + }) + } + + #[test] + fn rich_compare() { + Python::attach(|py| { + let lower = PyInt::new(py, 1); + let upper = PyInt::new(py, 2); + assert!( + lower + .rich_compare(upper, CompareOp::Lt) + .unwrap() + .is_truthy() + .unwrap() + ); + }) + } + + #[test] + fn type_get_module_name() { + Python::attach(|py| { + assert_eq!( + py.get_type::().module().unwrap().to_str().unwrap(), + "builtins" + ); + }) + } + + #[test] + fn 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(), core::ptr::null_mut()), + ) + } + .unwrap(); + assert!(dict.get_item("foo").is_ok()); + }) + } +} diff --git a/crates/vm/src/builtins/mod.rs b/crates/vm/src/builtins/mod.rs index 099787332c9..27b52dacb15 100644 --- a/crates/vm/src/builtins/mod.rs +++ b/crates/vm/src/builtins/mod.rs @@ -56,6 +56,7 @@ pub(crate) mod namespace; pub use namespace::PyNamespace; pub(crate) mod object; pub use object::PyBaseObject; +pub use object::{object_generic_set_dict, object_get_dict}; pub(crate) mod property; pub use property::PyProperty; #[path = "bool.rs"] diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index a41d4dea4e4..147e215a0cb 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -555,7 +555,7 @@ impl PyBaseObject { } } -pub(crate) fn object_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { +pub fn object_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { if let Some(dict) = obj.dict() { Ok(dict) } else { @@ -578,7 +578,7 @@ pub(crate) fn object_set_dict( .map_err(|_| vm.new_attribute_error("This object has no __dict__")) } -pub(crate) fn object_generic_set_dict( +pub fn object_generic_set_dict( obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine, diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 837023fa389..57b190ca324 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -1445,6 +1445,12 @@ impl PyObject { self.downgrade_with_typ(callback, vm.ctx.types.weakref_type.to_owned(), vm) } + pub fn clear_weak_refs(&self) { + if let Some(wrl) = self.weak_ref_list() { + wrl.clear(self); + } + } + pub fn get_weak_references(&self) -> Option>> { self.weak_ref_list() .map(|wrl| wrl.get_weak_references(self))