Split rustobject module.

This commit is contained in:
Daniel Grunwald 2015-06-22 00:35:01 +02:00
parent ba0643c11c
commit 6e1fc35177
7 changed files with 265 additions and 140 deletions

View File

@ -16,6 +16,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#![feature(const_fn)]
#![feature(unsafe_no_drop_flag)] // crucial so that PyObject<'p> is binary compatible with *mut ffi::PyObject
#![feature(filling_drop)] // necessary to avoid segfault with unsafe_no_drop_flag
#![feature(optin_builtin_traits)] // for opting out of Sync/Send
@ -87,11 +88,13 @@ pub use ffi::Py_ssize_t;
pub use err::{PyErr, PyResult};
pub use objects::*;
pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectWithTypeObject, ToPythonPointer};
pub use pythonrun::{GILGuard, prepare_freethreaded_python};
pub use pythonrun::{GILGuard, GILProtected, prepare_freethreaded_python};
pub use conversion::{FromPyObject, ToPyObject};
pub use objectprotocol::{ObjectProtocol};
#[cfg(feature="python27-sys")]
pub use rustobject::{PyRustTypeBuilder, PyRustType, PyRustObject};
pub use rustobject::{PyRustType, PyRustObject};
#[cfg(feature="python27-sys")]
pub use rustobject::typebuilder::PyRustTypeBuilder;
/// Constructs a `&'static CStr` literal.
macro_rules! cstr(

View File

@ -123,9 +123,9 @@ impl <'p> PyModule<'p> {
/// sets `new_type.__module__` to this module's name.
/// The new type will be added to this module when `finish()` is called on the builder.
#[cfg(feature="python27-sys")]
pub fn add_type<T>(&self, name: &str) -> ::rustobject::PyRustTypeBuilder<'p, T>
pub fn add_type<T>(&self, name: &str) -> ::rustobject::typebuilder::PyRustTypeBuilder<'p, T>
where T: 'p + Send {
::rustobject::new_typebuilder_for_module(self, name)
::rustobject::typebuilder::new_typebuilder_for_module(self, name)
}
}

View File

@ -18,7 +18,8 @@
use std::sync::{Once, ONCE_INIT};
use ffi;
use python::Python;
use python::{Python, ToPythonPointer};
use objects::PyObject;
static START: Once = ONCE_INIT;
@ -89,8 +90,34 @@ impl GILGuard {
}
/// Retrieves the marker type that proves that the GIL was acquired.
#[inline]
pub fn python<'p>(&'p self) -> Python<'p> {
unsafe { Python::assume_gil_acquired() }
}
}
/// Mutex-like wrapper object for data that is protected by the python GIL.
pub struct GILProtected<T> {
data: T
}
unsafe impl<T: Send> Send for GILProtected<T> { }
unsafe impl<T: Send> Sync for GILProtected<T> { }
impl <T> GILProtected<T> {
#[inline]
pub const fn new(data: T) -> GILProtected<T> {
GILProtected { data: data }
}
#[inline]
pub fn get<'p>(&'p self, py: Python<'p>) -> &'p T {
&self.data
}
#[inline]
pub fn into_inner(self) -> T {
self.data
}
}

24
src/rustobject/method.rs Normal file
View File

@ -0,0 +1,24 @@
use std::ptr;
use std::cell::RefCell;
use python::Python;
use pythonrun::{GILProtected};
use objects::{PyObject, PyType};
use ffi;
use super::PyRustType;
use super::typebuilder::PyRustTypeBuilder;
/*
struct MethodDescriptor<'p> {
ty: PyType<'p>,
name: PyObject<'p>
// d_method
}
static METHOD_DESCRIPTOR: GILProtected<RefCell<Option<SendablePyObject>>> = GILProtected::new(RefCell::new(None));
fn get_method_descriptor_type<'p>(py: Python<'p>) -> PyRustType<'p, MethodDescriptor<'p>> {
METHOD_DESCRIPTOR.get(py);
}
*/

View File

@ -19,10 +19,14 @@
use libc;
use ffi;
use python::{Python, ToPythonPointer, PythonObject};
use conversion::ToPyObject;
use objects::{PyObject, PyType, PyString, PyModule, PyDict};
use std::{mem, ops, ptr, marker};
use err::{self, PyResult};
pub mod typebuilder;
mod method;
/// A PythonObject that is usable as a base type with PyTypeBuilder::base().
pub trait PythonBaseObject<'p> : PythonObject<'p> {
/// Gets the size of the object, in bytes.
@ -57,7 +61,14 @@ impl <'p> PythonBaseObject<'p> for PyObject<'p> {
}
unsafe fn dealloc(ptr: *mut ffi::PyObject) {
((*ffi::Py_TYPE(ptr)).tp_free.unwrap())(ptr as *mut libc::c_void);
let ty = ffi::Py_TYPE(ptr);
((*ty).tp_free.unwrap())(ptr as *mut libc::c_void);
// For heap types, tp_alloc calls INCREF on the type objects,
// but tp_free points directly to the memory deallocator and does not call DECREF.
// So we'll do that manually here:
if ((*ty).tp_flags & ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}
@ -66,14 +77,13 @@ impl <'p> PythonBaseObject<'p> for PyObject<'p> {
/// Note that this type effectively acts like `Rc<T>`,
/// except that the reference counting is done by the python runtime.
#[repr(C)]
pub struct PyRustObject<'p, T, B = PyObject<'p>> where T: 'p, B: PythonBaseObject<'p> {
pub struct PyRustObject<'p, T, B = PyObject<'p>> where T: 'static, B: PythonBaseObject<'p> {
obj: PyObject<'p>,
/// The PyRustObject acts like a shared reference to the contained T.
t: marker::PhantomData<&'p T>,
b: marker::PhantomData<B>
t: marker::PhantomData<&'p (T, B)>
}
impl <'p, T, B> PyRustObject<'p, T, B> where T: 'p, B: PythonBaseObject<'p> {
impl <'p, T, B> PyRustObject<'p, T, B> where T: 'static + Send, B: PythonBaseObject<'p> {
#[inline] // this function can usually be reduced to a compile-time constant
fn offset() -> usize {
let align = mem::min_align_of::<T>();
@ -98,7 +108,7 @@ impl <'p, T, B> PyRustObject<'p, T, B> where T: 'p, B: PythonBaseObject<'p> {
}
}
impl <'p, T, B> PythonBaseObject<'p> for PyRustObject<'p, T, B> where T: 'p + Send, B: PythonBaseObject<'p> {
impl <'p, T, B> PythonBaseObject<'p> for PyRustObject<'p, T, B> where T: 'static + Send, B: PythonBaseObject<'p> {
#[inline]
fn size() -> usize {
PyRustObject::<T, B>::offset() + mem::size_of::<T>()
@ -120,18 +130,17 @@ impl <'p, T, B> PythonBaseObject<'p> for PyRustObject<'p, T, B> where T: 'p + Se
}
}
impl <'p, T, B> Clone for PyRustObject<'p, T, B> where T: 'p + Send, B: PythonBaseObject<'p> {
impl <'p, T, B> Clone for PyRustObject<'p, T, B> where T: 'static + Send, B: PythonBaseObject<'p> {
#[inline]
fn clone(&self) -> Self {
PyRustObject {
obj: self.obj.clone(),
t: marker::PhantomData,
b: marker::PhantomData
t: marker::PhantomData
}
}
}
impl <'p, T, B> ToPythonPointer for PyRustObject<'p, T, B> where T: 'p + Send, B: PythonBaseObject<'p> {
impl <'p, T, B> ToPythonPointer for PyRustObject<'p, T, B> where T: 'static + Send, B: PythonBaseObject<'p> {
#[inline]
fn as_ptr(&self) -> *mut ffi::PyObject {
self.obj.as_ptr()
@ -143,7 +152,7 @@ impl <'p, T, B> ToPythonPointer for PyRustObject<'p, T, B> where T: 'p + Send, B
}
}
impl <'p, T, B> PythonObject<'p> for PyRustObject<'p, T, B> where T: 'p + Send, B: PythonBaseObject<'p> {
impl <'p, T, B> PythonObject<'p> for PyRustObject<'p, T, B> where T: 'static + Send, B: PythonBaseObject<'p> {
#[inline]
fn as_object(&self) -> &PyObject<'p> {
&self.obj
@ -160,8 +169,7 @@ impl <'p, T, B> PythonObject<'p> for PyRustObject<'p, T, B> where T: 'p + Send,
unsafe fn unchecked_downcast_from(obj: PyObject<'p>) -> Self {
PyRustObject {
obj: obj,
t: marker::PhantomData,
b: marker::PhantomData
t: marker::PhantomData
}
}
@ -252,124 +260,3 @@ impl <'p, T> PythonObject<'p> for PyRustType<'p, T> where T: 'p + Send {
}
}
#[repr(C)]
#[must_use]
pub struct PyRustTypeBuilder<'p, T, B = PyObject<'p>> where T: 'p + Send, B: PythonBaseObject<'p> {
type_obj: PyType<'p>,
target_module: Option<PyModule<'p>>,
ht: *mut ffi::PyHeapTypeObject,
phantom: marker::PhantomData<&'p (B, T)>
}
pub fn new_typebuilder_for_module<'p, T>(m: &PyModule<'p>, name: &str) -> PyRustTypeBuilder<'p, T>
where T: 'p + Send {
let b = PyRustTypeBuilder::new(m.python(), name);
if let Ok(mod_name) = m.name() {
b.dict().set_item("__module__", mod_name).ok();
}
PyRustTypeBuilder { target_module: Some(m.clone()), .. b }
}
unsafe extern "C" fn disabled_tp_new_callback
(subtype: *mut ffi::PyTypeObject, args: *mut ffi::PyObject, kwds: *mut ffi::PyObject)
-> *mut ffi::PyObject {
ffi::PyErr_SetString(ffi::PyExc_TypeError,
b"Cannot initialize rust object from python.\0" as *const u8 as *const libc::c_char);
ptr::null_mut()
}
unsafe extern "C" fn tp_dealloc_callback<'p, T, B>(obj: *mut ffi::PyObject)
where T: 'p + Send, B: PythonBaseObject<'p> {
abort_on_panic!({
PyRustObject::<T, B>::dealloc(obj)
});
}
impl <'p, T> PyRustTypeBuilder<'p, T> where T: 'p + Send {
/// Create a new type builder.
pub fn new(py: Python<'p>, name: &str) -> PyRustTypeBuilder<'p, T> {
unsafe {
let obj = (ffi::PyType_Type.tp_alloc.unwrap())(&mut ffi::PyType_Type, 0);
if obj.is_null() {
panic!("Out of memory")
}
let ht = obj as *mut ffi::PyHeapTypeObject;
(*ht).ht_name = PyString::new(py, name.as_bytes()).steal_ptr();
(*ht).ht_type.tp_name = ffi::PyString_AS_STRING((*ht).ht_name);
(*ht).ht_type.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HEAPTYPE;
(*ht).ht_type.tp_new = Some(disabled_tp_new_callback);
PyRustTypeBuilder {
type_obj: PyType::unchecked_downcast_from(PyObject::from_owned_ptr(py, obj)),
target_module: None,
ht: ht,
phantom: marker::PhantomData
}
}
}
pub fn base<T2, B2>(self, base_type: &PyRustType<'p, T2, B2>)
-> PyRustTypeBuilder<'p, T, PyRustObject<'p, T2, B2>>
where T2: 'p + Send, B2: PythonBaseObject<'p>
{
unsafe {
ffi::Py_XDECREF((*self.ht).ht_type.tp_base as *mut ffi::PyObject);
(*self.ht).ht_type.tp_base = base_type.as_type_ptr();
ffi::Py_INCREF(base_type.as_ptr());
}
PyRustTypeBuilder {
type_obj: self.type_obj,
target_module: self.target_module,
ht: self.ht,
phantom: marker::PhantomData
}
}
pub fn dict(&self) -> PyDict<'p> {
let py = self.type_obj.python();
unsafe {
if (*self.ht).ht_type.tp_dict.is_null() {
(*self.ht).ht_type.tp_dict = PyDict::new(py).steal_ptr();
}
PyDict::unchecked_downcast_from(PyObject::from_borrowed_ptr(py, (*self.ht).ht_type.tp_dict))
}
}
}
impl <'p, T, B> PyRustTypeBuilder<'p, T, B> where T: 'p + Send, B: PythonBaseObject<'p> {
pub fn finish(self) -> PyResult<'p, PyRustType<'p, T, B>> {
let py = self.type_obj.python();
unsafe {
(*self.ht).ht_type.tp_basicsize = PyRustObject::<T, B>::size() as ffi::Py_ssize_t;
(*self.ht).ht_type.tp_dealloc = Some(tp_dealloc_callback::<T, B>);
try!(err::error_on_minusone(py, ffi::PyType_Ready(self.type_obj.as_type_ptr())))
}
if let Some(m) = self.target_module {
// Register the new type in the target module
let name = unsafe { PyObject::from_borrowed_ptr(py, (*self.ht).ht_name) };
try!(m.dict().set_item(name, self.type_obj.as_object()));
}
Ok(PyRustType {
type_obj: self.type_obj,
phantom: marker::PhantomData
})
}
/// Set the doc string on the type being built.
pub fn doc(self, doc_str: &str) -> Self {
unsafe {
if !(*self.ht).ht_type.tp_doc.is_null() {
ffi::PyObject_Free((*self.ht).ht_type.tp_doc as *mut libc::c_void);
}
// ht_type.tp_doc must be allocated with PyObject_Malloc
let p = ffi::PyObject_Malloc((doc_str.len() + 1) as libc::size_t);
(*self.ht).ht_type.tp_doc = p as *const libc::c_char;
if p.is_null() {
panic!("Out of memory")
}
ptr::copy_nonoverlapping(doc_str.as_ptr(), p as *mut u8, doc_str.len() + 1);
}
self
}
}

View File

@ -0,0 +1,169 @@
use libc;
use ffi;
use python::{Python, ToPythonPointer, PythonObject};
use conversion::ToPyObject;
use objects::{PyObject, PyType, PyString, PyModule, PyDict};
use std::{mem, ops, ptr, marker};
use err::{self, PyResult};
use super::{PythonBaseObject, PyRustObject, PyRustType};
#[repr(C)]
#[must_use]
pub struct PyRustTypeBuilder<'p, T, B = PyObject<'p>> where T: 'static + Send, B: PythonBaseObject<'p> {
type_obj: PyType<'p>,
target_module: Option<PyModule<'p>>,
ht: *mut ffi::PyHeapTypeObject,
phantom: marker::PhantomData<&'p (B, T)>
}
pub fn new_typebuilder_for_module<'p, T>(m: &PyModule<'p>, name: &str) -> PyRustTypeBuilder<'p, T>
where T: 'static + Send {
let b = PyRustTypeBuilder::new(m.python(), name);
if let Ok(mod_name) = m.name() {
b.dict().set_item("__module__", mod_name).ok();
}
PyRustTypeBuilder { target_module: Some(m.clone()), .. b }
}
unsafe extern "C" fn disabled_tp_new_callback
(subtype: *mut ffi::PyTypeObject, args: *mut ffi::PyObject, kwds: *mut ffi::PyObject)
-> *mut ffi::PyObject {
ffi::PyErr_SetString(ffi::PyExc_TypeError,
b"Cannot initialize rust object from python.\0" as *const u8 as *const libc::c_char);
ptr::null_mut()
}
unsafe extern "C" fn tp_dealloc_callback<'p, T, B>(obj: *mut ffi::PyObject)
where T: 'static + Send, B: PythonBaseObject<'p> {
abort_on_panic!({
PyRustObject::<T, B>::dealloc(obj)
});
}
impl <'p, T> PyRustTypeBuilder<'p, T> where T: 'static + Send {
/// Create a new type builder.
///
/// py: proof that the GIL is held by the current thread.
/// name: name of the new type
pub fn new(py: Python<'p>, name: &str) -> PyRustTypeBuilder<'p, T> {
unsafe {
let obj = (ffi::PyType_Type.tp_alloc.unwrap())(&mut ffi::PyType_Type, 0);
if obj.is_null() {
panic!("Out of memory")
}
debug_assert!(ffi::Py_REFCNT(obj) == 1);
let ht = obj as *mut ffi::PyHeapTypeObject;
(*ht).ht_name = PyString::new(py, name.as_bytes()).steal_ptr();
(*ht).ht_type.tp_name = ffi::PyString_AS_STRING((*ht).ht_name);
(*ht).ht_type.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HEAPTYPE;
(*ht).ht_type.tp_new = Some(disabled_tp_new_callback);
PyRustTypeBuilder {
type_obj: PyType::unchecked_downcast_from(PyObject::from_owned_ptr(py, obj)),
target_module: None,
ht: ht,
phantom: marker::PhantomData
}
}
}
/// Sets the base class that this type is inheriting from.
pub fn base<T2, B2>(self, base_type: &PyRustType<'p, T2, B2>)
-> PyRustTypeBuilder<'p, T, PyRustObject<'p, T2, B2>>
where T2: 'static + Send, B2: PythonBaseObject<'p>
{
unsafe {
ffi::Py_XDECREF((*self.ht).ht_type.tp_base as *mut ffi::PyObject);
(*self.ht).ht_type.tp_base = base_type.as_type_ptr();
ffi::Py_INCREF(base_type.as_ptr());
}
PyRustTypeBuilder {
type_obj: self.type_obj,
target_module: self.target_module,
ht: self.ht,
phantom: marker::PhantomData
}
}
}
impl <'p, T, B> PyRustTypeBuilder<'p, T, B> where T: 'static + Send, B: PythonBaseObject<'p> {
/// Retrieves the type dictionary of the type being built.
pub fn dict(&self) -> PyDict<'p> {
let py = self.type_obj.python();
unsafe {
if (*self.ht).ht_type.tp_dict.is_null() {
(*self.ht).ht_type.tp_dict = PyDict::new(py).steal_ptr();
}
PyDict::unchecked_downcast_from(PyObject::from_borrowed_ptr(py, (*self.ht).ht_type.tp_dict))
}
}
/// Set the doc string on the type being built.
pub fn doc(self, doc_str: &str) -> Self {
unsafe {
if !(*self.ht).ht_type.tp_doc.is_null() {
ffi::PyObject_Free((*self.ht).ht_type.tp_doc as *mut libc::c_void);
}
// ht_type.tp_doc must be allocated with PyObject_Malloc
let p = ffi::PyObject_Malloc((doc_str.len() + 1) as libc::size_t);
(*self.ht).ht_type.tp_doc = p as *const libc::c_char;
if p.is_null() {
panic!("Out of memory")
}
ptr::copy_nonoverlapping(doc_str.as_ptr(), p as *mut u8, doc_str.len() + 1);
}
self
}
/// Adds a new member to the type.
pub fn add<M>(self, name: &str, val: M) -> Self
where M: TypeMember<'p, PyRustObject<'p, T, B>> {
self.dict().set_item(name, val.into_descriptor(&self.type_obj, name)).unwrap();
self
}
/// Finalize construction of the new type.
pub fn finish(self) -> PyResult<'p, PyRustType<'p, T, B>> {
let py = self.type_obj.python();
unsafe {
(*self.ht).ht_type.tp_basicsize = PyRustObject::<T, B>::size() as ffi::Py_ssize_t;
(*self.ht).ht_type.tp_dealloc = Some(tp_dealloc_callback::<T, B>);
try!(err::error_on_minusone(py, ffi::PyType_Ready(self.type_obj.as_type_ptr())))
}
if let Some(m) = self.target_module {
// Register the new type in the target module
let name = unsafe { PyObject::from_borrowed_ptr(py, (*self.ht).ht_name) };
try!(m.dict().set_item(name, self.type_obj.as_object()));
}
Ok(PyRustType {
type_obj: self.type_obj,
phantom: marker::PhantomData
})
}
}
/// Represents something that can be added as a member to a python class/type.
///
/// T: type of rust class used for instances of the python class/type.
pub trait TypeMember<'p, T> where T: PythonObject<'p> {
fn into_descriptor(self, ty: &PyType<'p>, name: &str) -> PyObject<'p>;
}
// TODO: does this cause trouble for coherence?
impl <'p, T, S> TypeMember<'p, T> for S where T: PythonObject<'p>, S: ToPyObject<'p> {
#[inline]
fn into_descriptor(self, ty: &PyType<'p>, name: &str) -> PyObject<'p> {
self.into_py_object(ty.python()).into_object()
}
}
impl <'p, T> TypeMember<'p, T> for fn(&T) where T: PythonObject<'p> {
fn into_descriptor(self, ty: &PyType<'p>, name: &str) -> PyObject<'p> {
unimplemented!()
}
}

View File

@ -32,7 +32,22 @@ fn rustobject_calls_drop() {
fn rustobject_no_init_from_python() {
let gil = Python::acquire_gil();
let py = gil.python();
let t = PyRustTypeBuilder::<i32>::new(py, "TypeWithDrop").finish().unwrap();
let t = PyRustTypeBuilder::<i32>::new(py, "MyType").finish().unwrap();
assert!(t.call(&NoArgs, None).is_err());
}
#[test]
fn rustobject_heaptype_refcount() {
let gil = Python::acquire_gil();
let py = gil.python();
let t = PyRustTypeBuilder::<i32>::new(py, "MyType").finish().unwrap();
// TODO: investigate why the refcnt isn't 1.
//assert_eq!(1, t.as_object().get_refcnt());
let old_refcnt = t.as_object().get_refcnt();
let inst = t.create_instance(1, ());
assert_eq!(old_refcnt + 1, t.as_object().get_refcnt());
drop(inst);
assert_eq!(old_refcnt, t.as_object().get_refcnt());
}