// Copyright (c) 2017-present PyO3 Project and Contributors use std; use std::mem; use std::ffi::{CStr, CString}; use std::collections::HashMap; use {ffi, class}; use err::{PyErr, PyResult}; use python::Python; use objects::PyType; use callback::AbortOnDrop; use class::methods::PyMethodDefType; /// Basic python type information /// Implementing this trait for custom struct is enough to make it compatible with /// python object system pub trait PyTypeInfo { /// Type of objects to store in PyObject struct type Type; /// Size of the PyObject structure fn size() -> usize; /// `Type` instance offset inside PyObject structure fn offset() -> isize; /// Type name fn type_name() -> &'static str; /// Type description fn type_description() -> &'static str { "\0" } /// PyTypeObject instance for this type fn type_object() -> &'static mut ffi::PyTypeObject; } impl<'a, T: ?Sized> PyTypeInfo for &'a T where T: PyTypeInfo { type Type = T::Type; #[inline] default fn size() -> usize { ::size() } #[inline] default fn offset() -> isize { ::offset() } #[inline] default fn type_name() -> &'static str { ::type_name() } #[inline] default fn type_object() -> &'static mut ffi::PyTypeObject { ::type_object() } } pub trait PyObjectAlloc { /// Allocates a new object (usually by calling ty->tp_alloc), /// and initializes it using value. unsafe fn alloc(py: Python, value: T) -> PyResult<*mut ffi::PyObject>; /// Calls the rust destructor for the object and frees the memory /// (usually by calling ptr->ob_type->tp_free). /// This function is used as tp_dealloc implementation. unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject); } /// A Python object allocator that is usable as a base type for #[class] impl PyObjectAlloc for T where T : PyTypeInfo { /// Allocates a new object (usually by calling ty->tp_alloc), /// and initializes it using value. default unsafe fn alloc(py: Python, value: T) -> PyResult<*mut ffi::PyObject> { // TODO: remove this ::init_type(py); let obj = ffi::PyType_GenericAlloc( ::type_object(), 0); let offset = ::offset(); let ptr = (obj as *mut u8).offset(offset) as *mut T; std::ptr::write(ptr, value); Ok(obj) } /// Calls the rust destructor for the object and frees the memory /// (usually by calling ptr->ob_type->tp_free). /// This function is used as tp_dealloc implementation. default unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) { let ptr = (obj as *mut u8).offset(::offset()) as *mut T; std::ptr::drop_in_place(ptr); let ty = ffi::Py_TYPE(obj); if ffi::PyType_IS_GC(ty) != 0 { ffi::PyObject_GC_Del(obj as *mut ::c_void); } else { ffi::PyObject_Free(obj as *mut ::c_void); } // For heap types, PyType_GenericAlloc calls INCREF on the type objects, // so we need to call DECREF here: if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 { ffi::Py_DECREF(ty as *mut ffi::PyObject); } } } /// Trait implemented by Python object types that have a corresponding type object. pub trait PyTypeObject { /// Initialize type object fn init_type(py: Python); /// Retrieves the type object for this Python object type. fn type_object(py: Python) -> PyType; } impl PyTypeObject for T where T: PyObjectAlloc + PyTypeInfo { #[inline] default fn init_type(py: Python) { let mut ty = ::type_object(); if (ty.tp_flags & ffi::Py_TPFLAGS_READY) == 0 { // automatically initialize the class on-demand let to = initialize_type::( py, None, ::type_name(), ::type_description(), ty).expect( format!("An error occurred while initializing class {}", ::type_name()).as_ref()); py.release(to); } } #[inline] default fn type_object(py: Python) -> PyType { ::init_type(py); unsafe { PyType::from_type_ptr(py, ::type_object()) } } } pub fn initialize_type(py: Python, module_name: Option<&str>, type_name: &str, type_description: &'static str, type_object: &mut ffi::PyTypeObject) -> PyResult where T: PyObjectAlloc + PyTypeInfo { // type name let name = match module_name { Some(module_name) => CString::new(format!("{}.{}", module_name, type_name)), None => CString::new(type_name) }; let name = name.expect( "Module name/type name must not contain NUL byte").into_raw(); type_object.tp_name = name; type_object.tp_doc = type_description.as_ptr() as *const _; // dealloc type_object.tp_dealloc = Some(tp_dealloc_callback::); // type size type_object.tp_basicsize = ::size() as ffi::Py_ssize_t; // GC support ::update_type_object(type_object); // descriptor protocol ::tp_as_descr(type_object); // iterator methods ::tp_as_iter(type_object); // basic methods ::tp_as_object(type_object); // number methods if let Some(meth) = ::tp_as_number() { type_object.tp_as_number = Box::into_raw(Box::new(meth)); } else { type_object.tp_as_number = 0 as *mut ffi::PyNumberMethods; } // mapping methods if let Some(meth) = ::tp_as_mapping() { type_object.tp_as_mapping = Box::into_raw(Box::new(meth)); } else { type_object.tp_as_mapping = 0 as *mut ffi::PyMappingMethods; } // sequence methods if let Some(meth) = ::tp_as_sequence() { type_object.tp_as_sequence = Box::into_raw(Box::new(meth)); } else { type_object.tp_as_sequence = 0 as *mut ffi::PySequenceMethods; } // async methods async_methods::(type_object); // buffer protocol if let Some(meth) = ::tp_as_buffer() { type_object.tp_as_buffer = Box::into_raw(Box::new(meth)); } else { type_object.tp_as_buffer = 0 as *mut ffi::PyBufferProcs; } // normal methods let (new, call, mut methods) = py_class_method_defs::()?; if !methods.is_empty() { methods.push(ffi::PyMethodDef_INIT); type_object.tp_methods = methods.as_mut_ptr(); mem::forget(methods); } // __new__ method type_object.tp_new = new; // __call__ method type_object.tp_call = call; // properties let mut props = py_class_properties::(); if !props.is_empty() { props.push(ffi::PyGetSetDef_INIT); type_object.tp_getset = props.as_mut_ptr(); mem::forget(props); } // set type flags py_class_flags(type_object); // register type object unsafe { if ffi::PyType_Ready(type_object) == 0 { Ok(PyType::from_type_ptr(py, type_object)) } else { Err(PyErr::fetch(py)) } } } #[cfg(Py_3)] fn async_methods(type_info: &mut ffi::PyTypeObject) { if let Some(meth) = ::tp_as_async() { type_info.tp_as_async = Box::into_raw(Box::new(meth)); } else { type_info.tp_as_async = 0 as *mut ffi::PyAsyncMethods; } } #[cfg(not(Py_3))] fn async_methods(_type_info: &mut ffi::PyTypeObject) {} unsafe extern "C" fn tp_dealloc_callback(obj: *mut ffi::PyObject) where T: PyObjectAlloc { debug!("DEALLOC: {:?} - {:?}", obj, CStr::from_ptr((*(*obj).ob_type).tp_name).to_string_lossy()); let guard = AbortOnDrop("Cannot unwind out of tp_dealloc"); let py = Python::assume_gil_acquired(); let r = >::dealloc(py, obj); mem::forget(guard); r } #[cfg(Py_3)] fn py_class_flags(type_object: &mut ffi::PyTypeObject) { if type_object.tp_traverse != None || type_object.tp_clear != None { type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC; } else { type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT; } } #[cfg(not(Py_3))] fn py_class_flags(type_object: &mut ffi::PyTypeObject) { if type_object.tp_traverse != None || type_object.tp_clear != None { type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_CHECKTYPES | ffi::Py_TPFLAGS_HAVE_GC; } else { type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_CHECKTYPES; } if !type_object.tp_as_buffer.is_null() { type_object.tp_flags = type_object.tp_flags | ffi::Py_TPFLAGS_HAVE_NEWBUFFER; } } fn py_class_method_defs() -> PyResult<(Option, Option, Vec)> { let mut defs = Vec::new(); let mut call = None; let mut new = None; for def in ::py_methods() { match def { &PyMethodDefType::New(ref def) => { if let class::methods::PyMethodType::PyNewFunc(meth) = def.ml_meth { new = Some(meth) } }, &PyMethodDefType::Call(ref def) => { if let class::methods::PyMethodType::PyCFunctionWithKeywords(meth) = def.ml_meth { call = Some(meth) } else { panic!("Method type is not supoorted by tp_call slot") } } &PyMethodDefType::Method(ref def) => { defs.push(def.as_method_def()); } &PyMethodDefType::Class(ref def) => { defs.push(def.as_method_def()); } &PyMethodDefType::Static(ref def) => { defs.push(def.as_method_def()); } _ => (), } } for def in ::methods() { defs.push(def.as_method_def()); } for def in ::methods() { defs.push(def.as_method_def()); } for def in ::methods() { defs.push(def.as_method_def()); } for def in ::methods() { defs.push(def.as_method_def()); } for def in ::methods() { defs.push(def.as_method_def()); } py_class_async_methods::(&mut defs); Ok((new, call, defs)) } #[cfg(Py_3)] fn py_class_async_methods(defs: &mut Vec) { for def in ::methods() { defs.push(def.as_method_def()); } } #[cfg(not(Py_3))] fn py_class_async_methods(_defs: &mut Vec) {} fn py_class_properties() -> Vec { let mut defs = HashMap::new(); for def in ::py_methods() { match def { &PyMethodDefType::Getter(ref getter) => { let name = getter.name.to_string(); if !defs.contains_key(&name) { let _ = defs.insert(name.clone(), ffi::PyGetSetDef_INIT); } let def = defs.get_mut(&name).unwrap(); getter.copy_to(def); }, &PyMethodDefType::Setter(ref setter) => { let name = setter.name.to_string(); if !defs.contains_key(&name) { let _ = defs.insert(name.clone(), ffi::PyGetSetDef_INIT); } let def = defs.get_mut(&name).unwrap(); setter.copy_to(def); }, _ => (), } } defs.values().map(|i| i.clone()).collect() }