added properties support

This commit is contained in:
Nikolay Kim 2017-05-16 11:58:18 -07:00
parent 6d40d651a1
commit 9b1c4c55f4
15 changed files with 596 additions and 312 deletions

View file

@ -13,8 +13,10 @@ use proc_macro::TokenStream;
use quote::{Tokens, ToTokens};
mod py_class;
mod py_impl;
mod py_proto;
mod py_method;
mod utils;
#[proc_macro_attribute]
@ -23,7 +25,6 @@ pub fn proto(_: TokenStream, input: TokenStream) -> TokenStream {
let source = input.to_string();
// Parse the string representation into a syntax tree
//let ast: syn::Crate = source.parse().unwrap();
let mut ast = syn::parse_item(&source).unwrap();
// Build the output
@ -44,7 +45,6 @@ pub fn class(_: TokenStream, input: TokenStream) -> TokenStream {
let source = input.to_string();
// Parse the string representation into a syntax tree
//let ast: syn::Crate = source.parse().unwrap();
let mut ast = syn::parse_derive_input(&source).unwrap();
// Build the output
@ -57,3 +57,22 @@ pub fn class(_: TokenStream, input: TokenStream) -> TokenStream {
TokenStream::from_str(s.as_str()).unwrap()
}
#[proc_macro_attribute]
pub fn methods(_: TokenStream, input: TokenStream) -> TokenStream {
// Construct a string representation of the type definition
let source = input.to_string();
// Parse the string representation into a syntax tree
let mut ast = syn::parse_item(&source).unwrap();
// Build the output
let expanded = py_impl::build_py_methods(&mut ast);
// Return the generated impl as a TokenStream
let mut tokens = Tokens::new();
ast.to_tokens(&mut tokens);
let s = String::from(tokens.as_str()) + expanded.as_str();
TokenStream::from_str(s.as_str()).unwrap()
}

View file

@ -133,7 +133,7 @@ fn impl_checked_downcast(cls: &syn::Ident) -> Tokens {
fn impl_class_init(cls: &syn::Ident) -> Tokens {
quote! {
impl pyo3::class::methods::PyClassInit for #cls {
impl pyo3::class::typeob::PyClassInit for #cls {
fn init() -> bool {
true
}

58
pyo3cls/src/py_impl.rs Normal file
View file

@ -0,0 +1,58 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use syn;
use quote::Tokens;
use py_method;
pub fn build_py_methods(ast: &mut syn::Item) -> Tokens {
match ast.node {
syn::ItemKind::Impl(_, _, _, ref path, ref ty, ref mut impl_items) => {
if let &Some(_) = path {
panic!("#[methods] can not be used only with trait impl block");
} else {
impl_methods(ty, impl_items)
}
},
_ => panic!("#[methods] can only be used with Impl blocks"),
}
}
fn impl_methods(ty: &Box<syn::Ty>, impls: &mut Vec<syn::ImplItem>) -> Tokens {
// get method names in impl block
let mut methods = Vec::new();
for iimpl in impls.iter_mut() {
match iimpl.node {
syn::ImplItemKind::Method(ref mut sig, ref mut block) => {
methods.push(py_method::gen_py_method(
ty, &iimpl.ident, sig, block, &mut iimpl.attrs));
},
_ => (),
}
}
let tokens = quote! {
impl pyo3::class::methods::PyMethodsProtocolImpl for #ty {
fn py_methods() -> &'static [pyo3::class::PyMethodDefType] {
static METHODS: &'static [pyo3::class::PyMethodDefType] = &[
#(#methods),*
];
METHODS
}
}
};
let dummy_const = syn::Ident::new("_IMPL_PYO3_METHODS");
quote! {
#[feature(specialization)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
const #dummy_const: () = {
extern crate pyo3;
use pyo3::ffi;
#tokens
};
}
}

View file

@ -2,6 +2,8 @@
use syn;
use quote::Tokens;
use utils::for_err_msg;
#[derive(Debug)]
struct Arg<'a> {
@ -11,13 +13,22 @@ struct Arg<'a> {
pub optional: Option<&'a syn::Ty>,
}
#[derive(PartialEq, Debug)]
enum FnType {
Getter(Option<String>),
Setter(Option<String>),
Fn,
}
pub fn gen_py_method<'a>(cls: &Box<syn::Ty>, name: &syn::Ident,
sig: &mut syn::MethodSig, _block: &mut syn::Block,
_attrs: &Vec<syn::Attribute>) -> Tokens
meth_attrs: &mut Vec<syn::Attribute>) -> Tokens
{
check_generic(name, sig);
let fn_type = parse_attributes(meth_attrs);
//let mut has_self = false;
let mut py = false;
let mut arguments: Vec<Arg> = Vec::new();
@ -50,10 +61,103 @@ pub fn gen_py_method<'a>(cls: &Box<syn::Ty>, name: &syn::Ident,
}
}
impl_py_method_def(name, &impl_wrap(cls, name, arguments))
match fn_type {
FnType::Fn =>
impl_py_method_def(name, &impl_wrap(cls, name, arguments)),
FnType::Getter(getter) =>
impl_py_getter_def(name, getter, &impl_wrap_getter(cls, name, arguments)),
FnType::Setter(setter) =>
impl_py_setter_def(name, setter, &impl_wrap_setter(cls, name, arguments)),
}
}
fn check_generic(name: &syn::Ident, sig: &syn::MethodSig) {
fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> FnType {
let mut new_attrs = Vec::new();
let mut res: Option<FnType> = None;
for attr in attrs.iter() {
match attr.value {
syn::MetaItem::Word(ref name) => {
match name.as_ref() {
"setter" | "getter" => {
if attr.style == syn::AttrStyle::Inner {
panic!("Inner style attribute is not
supported for setter and getter");
}
if res != None {
panic!("setter/getter attribute can not be used mutiple times");
}
if name.as_ref() == "setter" {
res = Some(FnType::Setter(None))
} else {
res = Some(FnType::Getter(None))
}
},
_ => {
new_attrs.push(attr.clone())
}
}
},
syn::MetaItem::List(ref name, ref meta) => {
match name.as_ref() {
"setter" | "getter" => {
if attr.style == syn::AttrStyle::Inner {
panic!("Inner style attribute is not
supported for setter and getter");
}
if res != None {
panic!("setter/getter attribute can not be used mutiple times");
}
if meta.len() != 1 {
panic!("setter/getter requires one value");
}
match *meta.first().unwrap() {
syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref w)) => {
if name.as_ref() == "setter" {
res = Some(FnType::Setter(Some(w.to_string())))
} else {
res = Some(FnType::Getter(Some(w.to_string())))
}
},
syn::NestedMetaItem::Literal(ref lit) => {
match *lit {
syn::Lit::Str(ref s, syn::StrStyle::Cooked) => {
if name.as_ref() == "setter" {
res = Some(FnType::Setter(Some(s.clone())))
} else {
res = Some(FnType::Getter(Some(s.clone())))
}
},
_ => {
panic!("setter/getter attribute requires str value");
},
}
}
_ => {
println!("cannot parse {:?} attribute: {:?}", name, meta);
},
}
},
_ => {
new_attrs.push(attr.clone())
}
}
},
syn::MetaItem::NameValue(_, _) => {
new_attrs.push(attr.clone())
},
}
}
attrs.clear();
attrs.extend(new_attrs);
match res {
Some(tp) => tp,
None => FnType::Fn,
}
}
fn check_generic(name: &syn::Ident, sig: &syn::MethodSig) {
if !sig.generics.ty_params.is_empty() {
panic!("python method can not be generic: {:?}", name);
}
@ -73,14 +177,18 @@ fn check_arg_ty_and_optional<'a>(name: &'a syn::Ident, ty: &'a syn::Ty) -> Optio
match segment.parameters {
syn::PathParameters::AngleBracketed(ref params) => {
if params.types.len() != 1 {
panic!("argument type is not supported by python method: {:?} ({:?})",
name, ty);
panic!("argument type is not supported by python method: {:?} ({:?}) {:?}",
for_err_msg(name),
for_err_msg(ty),
for_err_msg(path));
}
Some(&params.types[0])
},
_ => {
panic!("argument type is not supported by python method: {:?} ({:?})",
name, ty);
panic!("argument type is not supported by python method: {:?} ({:?}) {:?}",
for_err_msg(name),
for_err_msg(ty),
for_err_msg(path));
}
}
},
@ -91,8 +199,10 @@ fn check_arg_ty_and_optional<'a>(name: &'a syn::Ident, ty: &'a syn::Ty) -> Optio
}
},
_ => {
panic!("argument type is not supported by python method: {:?} ({:?})",
name, ty);
None
//panic!("argument type is not supported by python method: {:?} ({:?})",
//for_err_msg(name),
//for_err_msg(ty));
},
}
}
@ -128,6 +238,54 @@ fn impl_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, args: Vec<Arg>) -> Tokens {
}
}
/// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords)
fn impl_wrap_getter(cls: &Box<syn::Ty>, name: &syn::Ident, _args: Vec<Arg>) -> Tokens {
quote! {
unsafe extern "C" fn wrap (slf: *mut pyo3::ffi::PyObject,
_: *mut pyo3::c_void)
-> *mut pyo3::ffi::PyObject
{
const LOCATION: &'static str = concat!(
stringify!(#cls), ".getter_", stringify!(#name), "()");
pyo3::_detail::handle_callback(
LOCATION, pyo3::_detail::PyObjectCallbackConverter, |py|
{
let slf = pyo3::PyObject::from_borrowed_ptr(
py, slf).unchecked_cast_into::<#cls>();
let ret = slf.#name(py);
pyo3::PyDrop::release_ref(slf, py);
ret
})
}
}
}
/// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords)
fn impl_wrap_setter(cls: &Box<syn::Ty>, name: &syn::Ident, _args: Vec<Arg>) -> Tokens {
quote! {
unsafe extern "C" fn wrap(slf: *mut pyo3::ffi::PyObject,
value: *mut pyo3::ffi::PyObject,
_: *mut pyo3::c_void) -> pyo3::c_int
{
const LOCATION: &'static str = concat!(
stringify!(#cls), ".setter", stringify!(#name), "()");
pyo3::_detail::handle_callback(
LOCATION, pyo3::py_class::slots::UnitCallbackConverter, |py|
{
let slf = pyo3::PyObject::from_borrowed_ptr(py, slf)
.unchecked_cast_into::<#cls>();
let value = pyo3::PyObject::from_borrowed_ptr(py, value);
let ret = slf.#name(py, &value);
pyo3::PyDrop::release_ref(slf, py);
pyo3::PyDrop::release_ref(value, py);
ret.map(|o| ())
})
}
}
}
fn impl_call(cls: &Box<syn::Ty>, fname: &syn::Ident, args: &Vec<Arg>) -> Tokens {
let names: Vec<&syn::Ident> = args.iter().map(|item| item.name).collect();
quote! {
@ -223,14 +381,66 @@ fn impl_arg_param(arg: &Arg, body: &Tokens) -> Tokens {
}
fn impl_py_method_def(name: &syn::Ident, wrapper: &Tokens) -> Tokens {
quote! {{
#wrapper
quote! {
pyo3::class::PyMethodDefType::Method({
#wrapper
pyo3::class::PyMethodDef {
ml_name: stringify!(#name),
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
ml_doc: "",
}
}}
pyo3::class::PyMethodDef {
ml_name: stringify!(#name),
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
ml_doc: "",
}
})
}
}
fn impl_py_setter_def(name: &syn::Ident, setter: Option<String>, wrapper: &Tokens) -> Tokens {
let n = if let Some(ref name) = setter {
name.to_string()
} else {
let n = String::from(name.as_ref());
if n.starts_with("set_") {
n[4..].to_string()
} else {
n
}
};
quote! {
pyo3::class::PyMethodDefType::Setter({
#wrapper
pyo3::class::PySetterDef {
name: #n,
meth: wrap,
doc: "",
}
})
}
}
fn impl_py_getter_def(name: &syn::Ident, getter: Option<String>, wrapper: &Tokens) -> Tokens {
let n = if let Some(ref name) = getter {
name.to_string()
} else {
let n = String::from(name.as_ref());
if n.starts_with("get_") {
n[4..].to_string()
} else {
n
}
};
quote! {
pyo3::class::PyMethodDefType::Getter({
#wrapper
pyo3::class::PyGetterDef {
name: #n,
meth: wrap,
doc: "",
}
})
}
}

View file

@ -99,10 +99,10 @@ pub fn build_py_proto(ast: &mut syn::Item) -> Tokens {
}
}
} else {
panic!("#[py_proto] can only be used with protocol trait implementations")
panic!("#[proto] can only be used with protocol trait implementations")
}
},
_ => panic!("#[py_proto] can only be used with Impl blocks"),
_ => panic!("#[proto] can only be used with Impl blocks"),
}
}
@ -135,7 +135,7 @@ fn impl_protocol(name: &'static str,
syn::ImplItemKind::Method(ref mut sig, ref mut block) => {
if methods.methods.contains(&iimpl.ident.as_ref()) {
py_methods.push(py_method::gen_py_method(
ty, &iimpl.ident, sig, block, &iimpl.attrs));
ty, &iimpl.ident, sig, block, &mut iimpl.attrs));
} else {
meth.push(String::from(iimpl.ident.as_ref()));
@ -175,8 +175,8 @@ fn impl_protocol(name: &'static str,
METHODS
}
fn py_methods() -> &'static [pyo3::class::PyMethodDef] {
static METHODS: &'static [pyo3::class::PyMethodDef] = &[
fn py_methods() -> &'static [pyo3::class::PyMethodDefType] {
static METHODS: &'static [pyo3::class::PyMethodDefType] = &[
#(#py_methods),*
];
METHODS

9
pyo3cls/src/utils.rs Normal file
View file

@ -0,0 +1,9 @@
use quote::{Tokens, ToTokens};
pub fn for_err_msg(i: &ToTokens) -> String {
let mut tokens = Tokens::new();
i.to_tokens(&mut tokens);
tokens.as_str().to_string()
}

View file

@ -45,14 +45,14 @@ impl<P> PyContextProtocol for P {
pub trait PyContextProtocolImpl {
fn methods() -> &'static [&'static str];
fn py_methods() -> &'static [::class::PyMethodDef];
fn py_methods() -> &'static [::methods::PyMethodDefType];
}
impl<T> PyContextProtocolImpl for T {
default fn methods() -> &'static [&'static str] {
NO_METHODS
}
default fn py_methods() -> &'static [::class::PyMethodDef] {
default fn py_methods() -> &'static [::methods::PyMethodDefType] {
NO_PY_METHODS
}
}

View file

@ -7,7 +7,13 @@ use std::ffi::CString;
use ::{ffi, exc, class, py_class, PyErr, Python, PyResult, PythonObject};
use objects::PyType;
use function::AbortOnDrop;
use class::NO_PY_METHODS;
pub enum PyMethodDefType {
Method(PyMethodDef),
Getter(PyGetterDef),
Setter(PySetterDef),
}
#[derive(Copy, Clone)]
pub enum PyMethodType {
@ -23,12 +29,31 @@ pub struct PyMethodDef {
pub ml_doc: &'static str,
}
#[derive(Copy, Clone)]
pub struct PyGetterDef {
pub name: &'static str,
pub meth: ffi::getter,
pub doc: &'static str,
}
#[derive(Copy, Clone)]
pub struct PySetterDef {
pub name: &'static str,
pub meth: ffi::setter,
pub doc: &'static str,
}
unsafe impl Sync for PyMethodDef {}
unsafe impl Sync for ffi::PyMethodDef {}
unsafe impl Sync for PyGetterDef {}
unsafe impl Sync for PySetterDef {}
unsafe impl Sync for ffi::PyGetSetDef {}
impl PyMethodDef {
fn as_method_def(&self) -> ffi::PyMethodDef {
pub fn as_method_def(&self) -> ffi::PyMethodDef {
let meth = match self.ml_meth {
PyMethodType::PyCFunction(meth) => meth,
PyMethodType::PyCFunctionWithKeywords(meth) =>
@ -48,138 +73,33 @@ impl PyMethodDef {
}
}
pub trait PyClassInit {
fn init() -> bool;
fn type_object() -> &'static mut ffi::PyTypeObject;
fn build_type(py: Python,
module_name: Option<&str>,
type_object: &mut ffi::PyTypeObject) -> PyResult<PyType>;
}
impl<T> PyClassInit for T where T: PythonObject + py_class::BaseObject {
default fn init() -> bool { false }
default fn type_object() -> &'static mut ffi::PyTypeObject {
static mut TYPE_OBJECT: ffi::PyTypeObject = ffi::PyTypeObject_INIT;
unsafe {
&mut TYPE_OBJECT
}
}
default fn build_type(py: Python, module_name: Option<&str>,
type_object: &mut ffi::PyTypeObject) -> PyResult<PyType> {
// type name
let name = match module_name {
Some(module_name) => CString::new(
format!("{}.{}", module_name, stringify!(type_name))),
None => CString::new(stringify!(type_name))
};
let name = name.expect(
"Module name/type name must not contain NUL byte").into_raw();
type_object.tp_name = name;
// dealloc
type_object.tp_dealloc = Some(tp_dealloc_callback::<T>);
// GC support
<T as class::gc::PyGCProtocolImpl>::update_type_object(type_object);
// type size
type_object.tp_basicsize = <T as py_class::BaseObject>::size() as ffi::Py_ssize_t;
// number methods
if let Some(meth) = ffi::PyNumberMethods::new::<T>() {
static mut NB_METHODS: ffi::PyNumberMethods = ffi::PyNumberMethods_INIT;
*(unsafe { &mut NB_METHODS }) = meth;
type_object.tp_as_number = unsafe { &mut NB_METHODS };
} else {
type_object.tp_as_number = 0 as *mut ffi::PyNumberMethods;
}
// mapping methods
if let Some(meth) = ffi::PyMappingMethods::new::<T>() {
static mut MP_METHODS: ffi::PyMappingMethods = ffi::PyMappingMethods_INIT;
*(unsafe { &mut MP_METHODS }) = meth;
type_object.tp_as_mapping = unsafe { &mut MP_METHODS };
} else {
type_object.tp_as_mapping = 0 as *mut ffi::PyMappingMethods;
}
// sequence methods
if let Some(meth) = ffi::PySequenceMethods::new::<T>() {
static mut SQ_METHODS: ffi::PySequenceMethods = ffi::PySequenceMethods_INIT;
*(unsafe { &mut SQ_METHODS }) = meth;
type_object.tp_as_sequence = unsafe { &mut SQ_METHODS };
} else {
type_object.tp_as_sequence = 0 as *mut ffi::PySequenceMethods;
}
// async methods
if let Some(meth) = ffi::PyAsyncMethods::new::<T>() {
static mut ASYNC_METHODS: ffi::PyAsyncMethods = ffi::PyAsyncMethods_INIT;
*(unsafe { &mut ASYNC_METHODS }) = meth;
type_object.tp_as_async = unsafe { &mut ASYNC_METHODS };
} else {
type_object.tp_as_async = 0 as *mut ffi::PyAsyncMethods;
}
// buffer protocol
if let Some(meth) = ffi::PyBufferProcs::new::<T>() {
static mut BUFFER_PROCS: ffi::PyBufferProcs = ffi::PyBufferProcs_INIT;
*(unsafe { &mut BUFFER_PROCS }) = meth;
type_object.tp_as_buffer = unsafe { &mut BUFFER_PROCS };
} else {
type_object.tp_as_buffer = 0 as *mut ffi::PyBufferProcs;
}
// normal methods
let mut methods = class::methods::py_class_method_defs::<T>();
if !methods.is_empty() {
methods.push(ffi::PyMethodDef_INIT);
type_object.tp_methods = methods.as_ptr() as *mut _;
static mut METHODS: *const ffi::PyMethodDef = 0 as *const _;
*(unsafe { &mut METHODS }) = methods.as_ptr();
}
// register type object
unsafe {
if ffi::PyType_Ready(type_object) == 0 {
Ok(PyType::from_type_ptr(py, type_object))
} else {
Err(PyErr::fetch(py))
}
impl PyGetterDef {
pub fn copy_to(&self, dst: &mut ffi::PyGetSetDef) {
if dst.name.is_null() {
dst.name = CString::new(self.name).expect(
"Method name must not contain NULL byte").into_raw();
}
dst.get = Some(self.meth.clone());
}
}
pub unsafe extern "C" fn tp_dealloc_callback<T>(obj: *mut ffi::PyObject)
where T: py_class::BaseObject
{
let guard = AbortOnDrop("Cannot unwind out of tp_dealloc");
let py = Python::assume_gil_acquired();
let r = T::dealloc(py, obj);
mem::forget(guard);
r
impl PySetterDef {
pub fn copy_to(&self, dst: &mut ffi::PyGetSetDef) {
if dst.name.is_null() {
dst.name = CString::new(self.name).expect(
"Method name must not contain NULL byte").into_raw();
}
dst.set = Some(self.meth.clone());
}
}
pub fn py_class_method_defs<T>() -> Vec<ffi::PyMethodDef> {
let mut defs = Vec::new();
for def in <T as class::context::PyContextProtocolImpl>::py_methods() {
defs.push(def.as_method_def())
}
for def in <T as class::number::PyNumberProtocolImpl>::py_methods() {
defs.push(def.as_method_def())
}
defs
#[doc(hidden)]
pub trait PyMethodsProtocolImpl {
fn py_methods() -> &'static [PyMethodDefType];
}
impl<T> PyMethodsProtocolImpl for T {
default fn py_methods() -> &'static [PyMethodDefType] {
NO_PY_METHODS
}
}

View file

@ -10,6 +10,7 @@ pub mod methods;
pub mod number;
pub mod gc;
pub mod sequence;
pub mod typeob;
pub use self::async::*;
pub use self::buffer::*;
@ -19,9 +20,10 @@ pub use self::number::PyNumberProtocol;
pub use self::mapping::PyMappingProtocol;
pub use self::sequence::PySequenceProtocol;
pub use self::methods::{PyMethodDef, PyMethodType};
pub use self::methods::{PyMethodDef, PyMethodDefType, PyMethodType,
PyGetterDef, PySetterDef};
use self::gc::PyGCProtocolImpl;
pub static NO_METHODS: &'static [&'static str] = &[];
pub static NO_PY_METHODS: &'static [PyMethodDef] = &[];
pub static NO_PY_METHODS: &'static [PyMethodDefType] = &[];

View file

@ -232,14 +232,14 @@ impl<T> PyNumberProtocol for T where T: PythonObject {
#[doc(hidden)]
pub trait PyNumberProtocolImpl {
fn methods() -> &'static [&'static str];
fn py_methods() -> &'static [::class::PyMethodDef];
fn py_methods() -> &'static [::class::PyMethodDefType];
}
impl<T> PyNumberProtocolImpl for T {
default fn methods() -> &'static [&'static str] {
NO_METHODS
}
default fn py_methods() -> &'static [::class::PyMethodDef] {
default fn py_methods() -> &'static [::class::PyMethodDefType] {
NO_PY_METHODS
}
}

202
src/class/typeob.rs Normal file
View file

@ -0,0 +1,202 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use std::mem;
use std::ptr;
use std::ffi::CString;
use std::collections::HashMap;
use ::{ffi, exc, class, py_class, PyErr, Python, PyResult, PythonObject};
use objects::PyType;
use function::AbortOnDrop;
use class::PyMethodDefType;
pub trait PyClassInit {
fn init() -> bool;
fn type_object() -> &'static mut ffi::PyTypeObject;
fn build_type(py: Python,
module_name: Option<&str>,
type_object: &mut ffi::PyTypeObject) -> PyResult<PyType>;
}
impl<T> PyClassInit for T where T: PythonObject + py_class::BaseObject {
default fn init() -> bool { false }
default fn type_object() -> &'static mut ffi::PyTypeObject {
static mut TYPE_OBJECT: ffi::PyTypeObject = ffi::PyTypeObject_INIT;
unsafe {
&mut TYPE_OBJECT
}
}
default fn build_type(py: Python, module_name: Option<&str>,
type_object: &mut ffi::PyTypeObject) -> PyResult<PyType> {
// type name
let name = match module_name {
Some(module_name) => CString::new(
format!("{}.{}", module_name, stringify!(type_name))),
None => CString::new(stringify!(type_name))
};
let name = name.expect(
"Module name/type name must not contain NUL byte").into_raw();
type_object.tp_name = name;
// dealloc
type_object.tp_dealloc = Some(tp_dealloc_callback::<T>);
// GC support
<T as class::gc::PyGCProtocolImpl>::update_type_object(type_object);
// type size
type_object.tp_basicsize = <T as py_class::BaseObject>::size() as ffi::Py_ssize_t;
// number methods
if let Some(meth) = ffi::PyNumberMethods::new::<T>() {
static mut NB_METHODS: ffi::PyNumberMethods = ffi::PyNumberMethods_INIT;
*(unsafe { &mut NB_METHODS }) = meth;
type_object.tp_as_number = unsafe { &mut NB_METHODS };
} else {
type_object.tp_as_number = 0 as *mut ffi::PyNumberMethods;
}
// mapping methods
if let Some(meth) = ffi::PyMappingMethods::new::<T>() {
static mut MP_METHODS: ffi::PyMappingMethods = ffi::PyMappingMethods_INIT;
*(unsafe { &mut MP_METHODS }) = meth;
type_object.tp_as_mapping = unsafe { &mut MP_METHODS };
} else {
type_object.tp_as_mapping = 0 as *mut ffi::PyMappingMethods;
}
// sequence methods
if let Some(meth) = ffi::PySequenceMethods::new::<T>() {
static mut SQ_METHODS: ffi::PySequenceMethods = ffi::PySequenceMethods_INIT;
*(unsafe { &mut SQ_METHODS }) = meth;
type_object.tp_as_sequence = unsafe { &mut SQ_METHODS };
} else {
type_object.tp_as_sequence = 0 as *mut ffi::PySequenceMethods;
}
// async methods
if let Some(meth) = ffi::PyAsyncMethods::new::<T>() {
static mut ASYNC_METHODS: ffi::PyAsyncMethods = ffi::PyAsyncMethods_INIT;
*(unsafe { &mut ASYNC_METHODS }) = meth;
type_object.tp_as_async = unsafe { &mut ASYNC_METHODS };
} else {
type_object.tp_as_async = 0 as *mut ffi::PyAsyncMethods;
}
// buffer protocol
if let Some(meth) = ffi::PyBufferProcs::new::<T>() {
static mut BUFFER_PROCS: ffi::PyBufferProcs = ffi::PyBufferProcs_INIT;
*(unsafe { &mut BUFFER_PROCS }) = meth;
type_object.tp_as_buffer = unsafe { &mut BUFFER_PROCS };
} else {
type_object.tp_as_buffer = 0 as *mut ffi::PyBufferProcs;
}
// normal methods
let mut methods = py_class_method_defs::<T>();
if !methods.is_empty() {
methods.push(ffi::PyMethodDef_INIT);
type_object.tp_methods = methods.as_ptr() as *mut _;
static mut METHODS: *const ffi::PyMethodDef = 0 as *const _;
*(unsafe { &mut METHODS }) = methods.as_ptr();
}
// properties
let mut props = py_class_properties::<T>();
if !props.is_empty() {
props.push(ffi::PyGetSetDef_INIT);
let props = props.into_boxed_slice();
type_object.tp_getset = props.as_ptr() as *mut _;
static mut PROPS: *const ffi::PyGetSetDef = 0 as *const _;
*(unsafe { &mut PROPS }) = props.as_ptr();
// strange
mem::forget(props);
}
// register type object
unsafe {
if ffi::PyType_Ready(type_object) == 0 {
Ok(PyType::from_type_ptr(py, type_object))
} else {
Err(PyErr::fetch(py))
}
}
}
}
pub unsafe extern "C" fn tp_dealloc_callback<T>(obj: *mut ffi::PyObject)
where T: py_class::BaseObject
{
let guard = AbortOnDrop("Cannot unwind out of tp_dealloc");
let py = Python::assume_gil_acquired();
let r = T::dealloc(py, obj);
mem::forget(guard);
r
}
fn py_class_method_defs<T>() -> Vec<ffi::PyMethodDef> {
let mut defs = Vec::new();
for def in <T as class::context::PyContextProtocolImpl>::py_methods() {
match def {
&PyMethodDefType::Method(ref def) => defs.push(def.as_method_def()),
_ => (),
}
}
for def in <T as class::number::PyNumberProtocolImpl>::py_methods() {
match def {
&PyMethodDefType::Method(ref def) => defs.push(def.as_method_def()),
_ => (),
}
}
for def in <T as class::methods::PyMethodsProtocolImpl>::py_methods() {
match def {
&PyMethodDefType::Method(ref def) => defs.push(def.as_method_def()),
_ => (),
}
}
defs
}
fn py_class_properties<T>() -> Vec<ffi::PyGetSetDef> {
let mut defs = HashMap::new();
for def in <T as class::methods::PyMethodsProtocolImpl>::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()
}

View file

@ -1,20 +1,19 @@
use std::ptr;
use std::os::raw::{c_void, c_char, c_int};
use ffi::object::{PyObject, PyTypeObject};
use ffi::structmember::PyMemberDef;
use ffi::methodobject::PyMethodDef;
pub type getter =
unsafe extern "C" fn
(slf: *mut PyObject, closure: *mut c_void)
-> *mut PyObject;
unsafe extern "C" fn(slf: *mut PyObject, closure: *mut c_void)
-> *mut PyObject;
pub type setter =
unsafe extern "C" fn
(slf: *mut PyObject, value: *mut PyObject,
closure: *mut c_void) -> c_int;
unsafe extern "C" fn(slf: *mut PyObject, value: *mut PyObject,
closure: *mut c_void) -> c_int;
#[repr(C)]
#[derive(Copy)]
#[derive(Copy, Debug)]
pub struct PyGetSetDef {
pub name: *mut c_char,
pub get: Option<getter>,
@ -23,6 +22,14 @@ pub struct PyGetSetDef {
pub closure: *mut c_void,
}
pub const PyGetSetDef_INIT : PyGetSetDef = PyGetSetDef {
name: ptr::null_mut(),
get: None,
set: None,
doc: ptr::null_mut(),
closure: ptr::null_mut(),
};
impl Clone for PyGetSetDef {
#[inline] fn clone(&self) -> PyGetSetDef { *self }
}

View file

@ -20,7 +20,6 @@ mod py_class;
mod py_class_impl;
#[doc(hidden)] pub mod slots;
#[doc(hidden)] pub mod members;
#[doc(hidden)] pub mod properties;
use std::os::raw::c_void;
use std::{mem, ptr, cell};

View file

@ -1,141 +0,0 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use ffi;
pub fn type_error_to_unit(py: ::Python, e: ::PyErr) -> ::PyResult<()> {
if e.matches(py, py.get_type::<::exc::TypeError>()) {
Ok(())
} else {
Err(e)
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_init_properties {
($class:ident, $py:ident, $type_object: ident, { }) => {{}};
($class:ident, $py:ident, $type_object: ident, { $( $prop:expr; )+ }) =>
{ unsafe {
let mut defs = Vec::new();
$(defs.push($prop);)+
defs.push(
$crate::_detail::ffi::PyGetSetDef {
name: 0 as *mut $crate::_detail::libc::c_char,
get: None,
set: None,
doc: 0 as *mut $crate::_detail::libc::c_char,
closure: 0 as *mut $crate::_detail::libc::c_void,
});
let props = defs.into_boxed_slice();
$type_object.tp_getset =
props.as_ptr() as *mut $crate::_detail::ffi::PyGetSetDef;
use std::mem;
mem::forget(props);
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_property_impl {
({} $class:ident $py:ident $name:ident { $( $descr_name:ident = $descr_expr:expr; )* } ) =>
{{
let mut getset_def: $crate::_detail::ffi::PyGetSetDef =
$crate::_detail::ffi::PyGetSetDef {
name: 0 as *mut $crate::_detail::libc::c_char,
get: None,
set: None,
doc: 0 as *mut $crate::_detail::libc::c_char,
closure: 0 as *mut $crate::_detail::libc::c_void,
};
getset_def.name = concat!(stringify!($name), "\0").as_ptr() as *mut _;
$( getset_def.$descr_name = Some($descr_expr); )*
getset_def
}};
( { get (&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)* }
$class:ident $py:ident $name:ident { $( $descr_name:ident = $descr_expr:expr; )* } ) =>
{
py_class_property_impl!{
{ $($tail)* } $class $py $name
/* methods: */ {
$( $descr_name = $descr_expr; )*
get = {
unsafe extern "C" fn wrap_getter_method(
slf: *mut $crate::_detail::ffi::PyObject,
_: *mut $crate::_detail::libc::c_void)
-> *mut $crate::_detail::ffi::PyObject
{
const LOCATION: &'static str = concat!(
stringify!($class), ".getter_", stringify!($name), "()");
fn get($slf: &$class, $py: $crate::Python) -> $res_type {
$($body)*
};
$crate::_detail::handle_callback(
LOCATION, $crate::_detail::PyObjectCallbackConverter,
|py| {
let slf = $crate::PyObject::from_borrowed_ptr(
py, slf).unchecked_cast_into::<$class>();
let ret = get(&slf, py);
$crate::PyDrop::release_ref(slf, py);
ret
})
}
wrap_getter_method
};
}
}
};
( { set(&$slf:ident, $value:ident : $value_type:ty)
-> $res_type:ty { $( $body:tt )* } $($tail:tt)* }
$class:ident $py:ident $name:ident { $( $descr_name:ident = $descr_expr:expr; )* } ) =>
{
py_class_property_impl! {
{ $($tail)* } $class $py $name
/* methods: */ {
$( $descr_name = $descr_expr; )*
set = {
unsafe extern "C" fn wrap_setter_method(
slf: *mut $crate::_detail::ffi::PyObject,
value: *mut $crate::_detail::ffi::PyObject,
_: *mut $crate::_detail::libc::c_void)
-> $crate::_detail::libc::c_int
{
const LOCATION: &'static str = concat!(
stringify!($class), ".setter_", stringify!($name), "()");
fn set($slf: &$class,
$py: $crate::Python, $value: $value_type) -> $res_type {
$($body)*
};
$crate::_detail::handle_callback(
LOCATION, $crate::py_class::slots::UnitCallbackConverter, move |py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf)
.unchecked_cast_into::<$class>();
let value = $crate::PyObject::from_borrowed_ptr(py, value);
let ret = match <$value_type as $crate::FromPyObject>::extract(py, &value) {
Ok(value) => set(&slf, py, value),
Err(e) =>
$crate::py_class::properties::type_error_to_unit(py, e)
};
$crate::PyDrop::release_ref(slf, py);
$crate::PyDrop::release_ref(value, py);
ret
})
}
wrap_setter_method
};
}
}
};
}

View file

@ -126,8 +126,7 @@ macro_rules! py_class_impl {
fn init($py: $crate::Python, module_name: Option<&str>) -> $crate::PyResult<$crate::PyType> {
py_class_init_members!($class, $py, TYPE_OBJECT, $members);
py_class_init_properties!($class, $py, TYPE_OBJECT, $properties);
unsafe { <$class as $crate::class::methods::PyClassInit>
unsafe { <$class as $crate::class::typeob::PyClassInit>
::build_type($py, module_name, &mut TYPE_OBJECT) }
}
}