add base support for inheritance

This commit is contained in:
Nikolay Kim 2017-07-26 22:29:55 -07:00
parent 8227ce81e2
commit d567817035
6 changed files with 158 additions and 41 deletions

View File

@ -59,6 +59,8 @@ so that they can benefit from a freelist. `XXX` is a number of items for free li
participate in python garbage collector. If custom class contains references to other participate in python garbage collector. If custom class contains references to other
python object that can be collector `PyGCProtocol` trait has to be implemented. python object that can be collector `PyGCProtocol` trait has to be implemented.
* `weakref` - adds support for python weak references * `weakref` - adds support for python weak references
* `base=xxx.YYY` - use custom base class. It is not possible to call constructor
of base class at the moment. `xxx.YYY`, `xxx` - module name, `YYY` class name.
## Constructor ## Constructor

View File

@ -4,13 +4,13 @@ use std;
use std::collections::HashMap; use std::collections::HashMap;
use syn; use syn;
use quote::Tokens; use quote::{Tokens, ToTokens};
use utils; use utils;
pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens { pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens {
let (params, flags) = parse_attribute(attr); let (params, flags, base_cls) = parse_attribute(attr);
let doc = utils::get_doc(&ast.attrs, true); let doc = utils::get_doc(&ast.attrs, true);
let base = syn::Ident::from("_pyo3::PyObjectRef"); let base = syn::Ident::from("_pyo3::PyObjectRef");
@ -29,7 +29,7 @@ pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens {
} }
let dummy_const = syn::Ident::new(format!("_IMPL_PYO3_CLS_{}", ast.ident)); let dummy_const = syn::Ident::new(format!("_IMPL_PYO3_CLS_{}", ast.ident));
let tokens = impl_class(&ast.ident, &base, token, doc, params, flags); let tokens = impl_class(&ast.ident, &base, token, doc, params, flags, base_cls);
quote! { quote! {
#[allow(non_upper_case_globals, unused_attributes, #[allow(non_upper_case_globals, unused_attributes,
@ -45,7 +45,8 @@ pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens {
fn impl_class(cls: &syn::Ident, base: &syn::Ident, fn impl_class(cls: &syn::Ident, base: &syn::Ident,
token: Option<syn::Ident>, doc: syn::Lit, token: Option<syn::Ident>, doc: syn::Lit,
params: HashMap<&'static str, syn::Ident>, flags: Vec<syn::Ident>) -> Tokens { params: HashMap<&'static str, syn::Ident>,
flags: Vec<syn::Ident>, base_cls: Option<(String, String)>) -> Tokens {
let cls_name = match params.get("name") { let cls_name = match params.get("name") {
Some(name) => quote! { #name }.as_str().to_string(), Some(name) => quote! { #name }.as_str().to_string(),
None => quote! { #cls }.as_str().to_string() None => quote! { #cls }.as_str().to_string()
@ -112,7 +113,8 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
fn as_ptr(&self) -> *mut ffi::PyObject { fn as_ptr(&self) -> *mut ffi::PyObject {
unsafe { unsafe {
{self as *const _ as *mut u8} {self as *const _ as *mut u8}
.offset(-<#cls as _pyo3::typeob::PyTypeInfo>::OFFSET) as *mut ffi::PyObject .offset(-<#cls as _pyo3::typeob::PyTypeInfo>::offset())
as *mut ffi::PyObject
} }
} }
} }
@ -174,21 +176,85 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
syn::Ident::from("0") syn::Ident::from("0")
}; };
// base class
let offset_code = if let Some((m, cls_name)) = base_cls {
quote! {
#[inline]
unsafe fn base_type_object() -> &'static mut ffi::PyTypeObject {
static mut TYPE_OBJECT: *mut ffi::PyTypeObject = 0 as *mut ffi::PyTypeObject;
if TYPE_OBJECT.is_null() {
// load module
let gil = _pyo3::Python::acquire_gil();
let py = gil.python();
let imp = py.import(#m)
.expect(format!(
"Can not import module: {}", #m).as_ref());
let cls = imp.get(#cls_name)
.expect(format!(
"Can not load exception class: {}.{}", #m, #cls_name).as_ref());
TYPE_OBJECT = cls.into_ptr() as *mut ffi::PyTypeObject;
}
&mut *TYPE_OBJECT
}
#[inline]
fn offset() -> isize {
static mut OFFSET: isize = 0;
unsafe {
if OFFSET == 0 {
// round base_size up to next multiple of align
OFFSET = (
((Self::base_type_object().tp_basicsize as usize) +
std::mem::align_of::<#cls>()-1) /
std::mem::align_of::<#cls>() * std::mem::align_of::<#cls>())
as isize
}
OFFSET
}
}
}
} else {
quote! {
#[inline]
fn offset() -> isize {
static mut OFFSET: isize = 0;
unsafe {
if OFFSET == 0 {
// round base_size up to next multiple of align
OFFSET = (
(<#base as _pyo3::typeob::PyTypeInfo>::size() +
std::mem::align_of::<#cls>()-1) /
std::mem::align_of::<#cls>() * std::mem::align_of::<#cls>())
as isize
}
OFFSET
}
}
}
};
quote! { quote! {
impl _pyo3::typeob::PyTypeInfo for #cls { impl _pyo3::typeob::PyTypeInfo for #cls {
type Type = #cls; type Type = #cls;
const NAME: &'static str = #cls_name; const NAME: &'static str = #cls_name;
const DESCRIPTION: &'static str = #doc; const DESCRIPTION: &'static str = #doc;
const SIZE: usize = Self::OFFSET as usize + std::mem::size_of::<#cls>() + #weakref;
const OFFSET: isize = {
// round base_size up to next multiple of align
((<#base as _pyo3::typeob::PyTypeInfo>::SIZE + std::mem::align_of::<#cls>()-1) /
std::mem::align_of::<#cls>() * std::mem::align_of::<#cls>()) as isize
};
const FLAGS: usize = #(#flags)|*; const FLAGS: usize = #(#flags)|*;
#[inline]
fn size() -> usize {
static mut SIZE: usize = 0;
unsafe {
if SIZE == 0 {
SIZE = Self::offset() as usize + std::mem::size_of::<#cls>() + #weakref;
}
SIZE
}
}
#offset_code
#[inline] #[inline]
unsafe fn type_object() -> &'static mut _pyo3::ffi::PyTypeObject { unsafe fn type_object() -> &'static mut _pyo3::ffi::PyTypeObject {
static mut TYPE_OBJECT: _pyo3::ffi::PyTypeObject = _pyo3::ffi::PyTypeObject_INIT; static mut TYPE_OBJECT: _pyo3::ffi::PyTypeObject = _pyo3::ffi::PyTypeObject_INIT;
@ -233,7 +299,7 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
if checked { if checked {
let ptr = (ptr as *mut u8) let ptr = (ptr as *mut u8)
.offset(<#cls as _pyo3::typeob::PyTypeInfo>::OFFSET) as *mut #cls; .offset(<#cls as _pyo3::typeob::PyTypeInfo>::offset()) as *mut #cls;
Some(ptr.as_ref().unwrap()) Some(ptr.as_ref().unwrap())
} else { } else {
None None
@ -248,7 +314,7 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
if (*ptr).ob_type == <#cls as _pyo3::typeob::PyTypeInfo>::type_object() if (*ptr).ob_type == <#cls as _pyo3::typeob::PyTypeInfo>::type_object()
{ {
let ptr = (ptr as *mut u8) let ptr = (ptr as *mut u8)
.offset(<#cls as _pyo3::typeob::PyTypeInfo>::OFFSET) as *mut #cls; .offset(<#cls as _pyo3::typeob::PyTypeInfo>::offset()) as *mut #cls;
Some(ptr.as_ref().unwrap()) Some(ptr.as_ref().unwrap())
} else { } else {
None None
@ -260,14 +326,14 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
unsafe fn unchecked_downcast_from(ob: &_pyo3::PyObjectRef) -> &Self unsafe fn unchecked_downcast_from(ob: &_pyo3::PyObjectRef) -> &Self
{ {
let ptr = (ob.as_ptr() as *mut u8) let ptr = (ob.as_ptr() as *mut u8)
.offset(<#cls as _pyo3::typeob::PyTypeInfo>::OFFSET) as *mut #cls; .offset(<#cls as _pyo3::typeob::PyTypeInfo>::offset()) as *mut #cls;
&*ptr &*ptr
} }
#[inline] #[inline]
unsafe fn unchecked_mut_downcast_from(ob: &_pyo3::PyObjectRef) -> &mut Self unsafe fn unchecked_mut_downcast_from(ob: &_pyo3::PyObjectRef) -> &mut Self
{ {
let ptr = (ob.as_ptr() as *mut u8) let ptr = (ob.as_ptr() as *mut u8)
.offset(<#cls as _pyo3::typeob::PyTypeInfo>::OFFSET) as *mut #cls; .offset(<#cls as _pyo3::typeob::PyTypeInfo>::offset()) as *mut #cls;
&mut *ptr &mut *ptr
} }
} }
@ -282,7 +348,7 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
if checked { if checked {
let ptr = (ptr as *mut u8) let ptr = (ptr as *mut u8)
.offset(<#cls as _pyo3::typeob::PyTypeInfo>::OFFSET) as *mut #cls; .offset(<#cls as _pyo3::typeob::PyTypeInfo>::offset()) as *mut #cls;
Some(ptr.as_mut().unwrap()) Some(ptr.as_mut().unwrap())
} else { } else {
None None
@ -296,7 +362,7 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
if (*ptr).ob_type == <#cls as _pyo3::typeob::PyTypeInfo>::type_object() if (*ptr).ob_type == <#cls as _pyo3::typeob::PyTypeInfo>::type_object()
{ {
let ptr = (ptr as *mut u8) let ptr = (ptr as *mut u8)
.offset(<#cls as _pyo3::typeob::PyTypeInfo>::OFFSET) as *mut #cls; .offset(<#cls as _pyo3::typeob::PyTypeInfo>::offset()) as *mut #cls;
Some(ptr.as_mut().unwrap()) Some(ptr.as_mut().unwrap())
} else { } else {
None None
@ -321,9 +387,11 @@ fn is_python_token(field: &syn::Field) -> bool {
return false return false
} }
fn parse_attribute(attr: String) -> (HashMap<&'static str, syn::Ident>, Vec<syn::Ident>) { fn parse_attribute(attr: String) -> (HashMap<&'static str, syn::Ident>,
Vec<syn::Ident>, Option<(String, String)>) {
let mut params = HashMap::new(); let mut params = HashMap::new();
let mut flags = vec![syn::Ident::from("0")]; let mut flags = vec![syn::Ident::from("0")];
let mut base = None;
if let Ok(tts) = syn::parse_token_trees(&attr) { if let Ok(tts) = syn::parse_token_trees(&attr) {
let mut elem = Vec::new(); let mut elem = Vec::new();
@ -419,7 +487,17 @@ fn parse_attribute(attr: String) -> (HashMap<&'static str, syn::Ident>, Vec<syn:
} }
}, },
"base" => { "base" => {
let mut m = String::new();
for el in elem[2..elem.len()-1].iter() {
let mut t = Tokens::new();
el.to_tokens(&mut t);
m += t.as_str().trim();
}
m = m.trim_matches('.').to_owned();
let mut t = Tokens::new();
elem[elem.len()-1].to_tokens(&mut t);
let cls = t.as_str().trim().to_owned();
base = Some((m, cls));
}, },
_ => { _ => {
println!("Unsupported parameter: {:?}", key); println!("Unsupported parameter: {:?}", key);
@ -428,5 +506,5 @@ fn parse_attribute(attr: String) -> (HashMap<&'static str, syn::Ident>, Vec<syn:
} }
} }
(params, flags) (params, flags, base)
} }

View File

@ -81,14 +81,14 @@ impl<T> PyObjectAlloc<T> for T where T: PyObjectWithFreeList {
ffi::PyType_GenericAlloc(<T as PyTypeInfo>::type_object(), 0) ffi::PyType_GenericAlloc(<T as PyTypeInfo>::type_object(), 0)
}; };
let ptr = (obj as *mut u8).offset(<T as PyTypeInfo>::OFFSET) as *mut T; let ptr = (obj as *mut u8).offset(<T as PyTypeInfo>::offset()) as *mut T;
std::ptr::write(ptr, value); std::ptr::write(ptr, value);
Ok(obj) Ok(obj)
} }
unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) { unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) {
let ptr = (obj as *mut u8).offset(<T as PyTypeInfo>::OFFSET) as *mut T; let ptr = (obj as *mut u8).offset(<T as PyTypeInfo>::offset()) as *mut T;
std::ptr::drop_in_place(ptr); std::ptr::drop_in_place(ptr);
if let Some(obj) = <T as PyObjectWithFreeList>::get_free_list().insert(obj) { if let Some(obj) = <T as PyObjectWithFreeList>::get_free_list().insert(obj) {

View File

@ -206,14 +206,14 @@ impl<T> AsPyRef<T> for Py<T> where T: PyTypeInfo {
#[inline] #[inline]
default fn as_ref(&self, _py: Python) -> &T { default fn as_ref(&self, _py: Python) -> &T {
unsafe { unsafe {
let ptr = (self.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T; let ptr = (self.as_ptr() as *mut u8).offset(T::offset()) as *mut T;
ptr.as_ref().unwrap() ptr.as_ref().unwrap()
} }
} }
#[inline] #[inline]
default fn as_mut(&self, _py: Python) -> &mut T { default fn as_mut(&self, _py: Python) -> &mut T {
unsafe { unsafe {
let ptr = (self.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T; let ptr = (self.as_ptr() as *mut u8).offset(T::offset()) as *mut T;
ptr.as_mut().unwrap() ptr.as_mut().unwrap()
} }
} }

View File

@ -140,8 +140,23 @@ macro_rules! pyobject_nativetype(
impl $crate::typeob::PyTypeInfo for $name { impl $crate::typeob::PyTypeInfo for $name {
type Type = (); type Type = ();
const NAME: &'static str = stringify!($name); const NAME: &'static str = stringify!($name);
const SIZE: usize = $crate::std::mem::size_of::<$crate::ffi::PyObject>();
const OFFSET: isize = 0; #[inline]
fn size() -> usize {
static mut SIZE: usize = 0;
unsafe {
if SIZE == 0 {
SIZE = $crate::std::mem::size_of::<$crate::ffi::PyObject>();
}
SIZE
}
}
#[inline]
fn offset() -> isize {
0
}
#[inline] #[inline]
unsafe fn type_object() -> &'static mut $crate::ffi::PyTypeObject { unsafe fn type_object() -> &'static mut $crate::ffi::PyTypeObject {

View File

@ -26,15 +26,25 @@ pub trait PyTypeInfo {
/// Class doc string /// Class doc string
const DESCRIPTION: &'static str = "\0"; const DESCRIPTION: &'static str = "\0";
/// Size of the rust PyObject structure (PyObject + rust structure)
const SIZE: usize;
/// `Type` instance offset inside PyObject structure
const OFFSET: isize;
/// Type flags (ie PyType_GC, PyType_WeakRef) /// Type flags (ie PyType_GC, PyType_WeakRef)
const FLAGS: usize = 0; const FLAGS: usize = 0;
/// Size of the rust PyObject structure (PyObject + rust structure)
fn size() -> usize;
/// `Type` instance offset inside PyObject structure
fn offset() -> isize;
/// Base class
unsafe fn base_type_object() -> &'static mut ffi::PyTypeObject {
static mut TYPE_OBJECT: *mut ffi::PyTypeObject = std::ptr::null_mut();
if TYPE_OBJECT.is_null() {
TYPE_OBJECT = &mut ffi::PyBaseObject_Type;
}
&mut *TYPE_OBJECT
}
/// PyTypeObject instance for this type /// PyTypeObject instance for this type
unsafe fn type_object() -> &'static mut ffi::PyTypeObject; unsafe fn type_object() -> &'static mut ffi::PyTypeObject;
@ -54,10 +64,18 @@ impl<'a, T: ?Sized> PyTypeInfo for &'a T where T: PyTypeInfo {
type Type = T::Type; type Type = T::Type;
const NAME: &'static str = T::NAME; const NAME: &'static str = T::NAME;
const DESCRIPTION: &'static str = T::DESCRIPTION; const DESCRIPTION: &'static str = T::DESCRIPTION;
const SIZE: usize = T::SIZE;
const OFFSET: isize = T::OFFSET;
const FLAGS: usize = T::FLAGS; const FLAGS: usize = T::FLAGS;
#[inline]
default fn size() -> usize {
<T as PyTypeInfo>::size()
}
#[inline]
default fn offset() -> isize {
<T as PyTypeInfo>::offset()
}
#[inline] #[inline]
default unsafe fn type_object() -> &'static mut ffi::PyTypeObject { default unsafe fn type_object() -> &'static mut ffi::PyTypeObject {
<T as PyTypeInfo>::type_object() <T as PyTypeInfo>::type_object()
@ -91,14 +109,14 @@ impl<T> PyObjectAlloc<T> for T where T : PyTypeInfo {
let obj = ffi::PyType_GenericAlloc(T::type_object(), 0); let obj = ffi::PyType_GenericAlloc(T::type_object(), 0);
let ptr = (obj as *mut u8).offset(T::OFFSET) as *mut T; let ptr = (obj as *mut u8).offset(T::offset()) as *mut T;
std::ptr::write(ptr, value); std::ptr::write(ptr, value);
Ok(obj) Ok(obj)
} }
default unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) { default unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) {
let ptr = (obj as *mut u8).offset(T::OFFSET) as *mut T; let ptr = (obj as *mut u8).offset(T::offset()) as *mut T;
std::ptr::drop_in_place(ptr); std::ptr::drop_in_place(ptr);
let ty = ffi::Py_TYPE(obj); let ty = ffi::Py_TYPE(obj);
@ -165,17 +183,18 @@ pub fn initialize_type<'p, T>(py: Python<'p>, module_name: Option<&str>) -> PyRe
let type_object: &mut ffi::PyTypeObject = unsafe{&mut *T::type_object()}; let type_object: &mut ffi::PyTypeObject = unsafe{&mut *T::type_object()};
type_object.tp_name = name; type_object.tp_name = name;
type_object.tp_doc = T::DESCRIPTION.as_ptr() as *const _; type_object.tp_doc = T::DESCRIPTION.as_ptr() as *const _;
type_object.tp_base = unsafe{<T as PyTypeInfo>::base_type_object()};
// dealloc // dealloc
type_object.tp_dealloc = Some(tp_dealloc_callback::<T>); type_object.tp_dealloc = Some(tp_dealloc_callback::<T>);
// type size // type size
type_object.tp_basicsize = <T as PyTypeInfo>::SIZE as ffi::Py_ssize_t; type_object.tp_basicsize = <T as PyTypeInfo>::size() as ffi::Py_ssize_t;
// weakref support (check py3cls::py_class::impl_class) // weakref support (check py3cls::py_class::impl_class)
if T::FLAGS & PY_TYPE_FLAG_WEAKREF != 0 { if T::FLAGS & PY_TYPE_FLAG_WEAKREF != 0 {
type_object.tp_weaklistoffset = type_object.tp_weaklistoffset =
(T::SIZE - std::mem::size_of::<*const ffi::PyObject>()) as isize; (T::size() - std::mem::size_of::<*const ffi::PyObject>()) as isize;
} }
// GC support // GC support
@ -243,6 +262,9 @@ pub fn initialize_type<'p, T>(py: Python<'p>, module_name: Option<&str>) -> PyRe
// set type flags // set type flags
py_class_flags::<T>(type_object); py_class_flags::<T>(type_object);
if type_object.tp_base != unsafe{std::mem::transmute(&ffi::PyBaseObject_Type)} {
type_object.tp_flags = type_object.tp_flags | ffi::Py_TPFLAGS_HEAPTYPE
}
// register type object // register type object
unsafe { unsafe {
@ -281,7 +303,7 @@ fn py_class_flags<T: PyTypeInfo>(type_object: &mut ffi::PyTypeObject) {
if type_object.tp_traverse != None || type_object.tp_clear != None || if type_object.tp_traverse != None || type_object.tp_clear != None ||
T::FLAGS & PY_TYPE_FLAG_GC != 0 T::FLAGS & PY_TYPE_FLAG_GC != 0
{ {
type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC; type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC;
} else { } else {
type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT; type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT;
} }