better slot trait definition

This commit is contained in:
Nikolay Kim 2017-05-17 16:33:18 -07:00
parent c9aefd7e5f
commit 2eea45e9fa
6 changed files with 437 additions and 84 deletions

110
pyo3cls/src/func.rs Normal file
View file

@ -0,0 +1,110 @@
use syn;
use quote::Tokens;
// TODO:
// Add lifetime support for args with Rptr
pub enum MethodProto {
Len{name: &'static str, proto: &'static str},
//Unary(&'static str),
Binary{name: &'static str, arg: &'static str, proto: &'static str},
Ternary{name: &'static str, arg1: &'static str, arg2: &'static str, proto: &'static str},
}
impl MethodProto {
pub fn eq(&self, name: &str) -> bool {
match *self {
MethodProto::Len{name: n, proto: _} => n == name,
//MethodProto::Unary(n) => n == name,
MethodProto::Binary{name: n, arg: _, proto: _} => n == name,
MethodProto::Ternary{name: n, arg1: _, arg2: _, proto: _} => n == name,
}
}
}
pub fn impl_method_proto(cls: &Box<syn::Ty>,
sig: &syn::MethodSig,
meth: &MethodProto) -> Tokens {
match sig.decl.output {
syn::FunctionRetTy::Ty(ref ty) => {
match *meth {
MethodProto::Len{name: _, proto} => {
let p = syn::Ident::from(proto);
quote! {
impl #p for #cls {
type Result = #ty;
}
}
},
MethodProto::Binary{name: _, arg, proto} => {
let p = syn::Ident::from(proto);
let arg_name = syn::Ident::from(arg);
let arg_ty = get_arg_ty(sig, 2);
let succ = get_res_success(ty);
quote! {
impl #p for #cls {
type #arg_name = #arg_ty;
type Success = #succ;
type Result = #ty;
}
}
},
MethodProto::Ternary{name: _, arg1, arg2, proto} => {
let p = syn::Ident::from(proto);
let arg1_name = syn::Ident::from(arg1);
let arg1_ty = get_arg_ty(sig, 2);
let arg2_name = syn::Ident::from(arg2);
let arg2_ty = get_arg_ty(sig, 3);
let succ = get_res_success(ty);
quote! {
impl #p for #cls {
type #arg1_name = #arg1_ty;
type #arg2_name = #arg2_ty;
type Success = #succ;
type Result = #ty;
}
}
},
}
},
_ => panic!("not supported"),
}
}
fn get_arg_ty(sig: &syn::MethodSig, idx: usize) -> syn::Ty {
match sig.decl.inputs[idx] {
syn::FnArg::Captured(_, ref arg_ty) => {
arg_ty.clone()
},
_ =>
panic!("not supported"),
}
}
// Success
fn get_res_success(ty: &syn::Ty) -> syn::Ty {
match ty {
&syn::Ty::Path(_, ref path) => {
if let Some(segment) = path.segments.last() {
match segment.ident.as_ref() {
// check result type
"PyResult" => match segment.parameters {
syn::PathParameters::AngleBracketed(ref data) => {
data.types[0].clone()
},
_ => panic!("not supported"),
},
_ => panic!("not supported"),
}
} else {
panic!("not supported")
}
}
_ => panic!("not supported"),
}
}

View file

@ -16,6 +16,7 @@ mod py_class;
mod py_impl;
mod py_proto;
mod py_method;
mod func;
mod utils;

View file

@ -4,6 +4,7 @@ use syn;
use quote::{Tokens, ToTokens};
use py_method;
use func::{MethodProto, impl_method_proto};
struct Methods {
@ -12,6 +13,11 @@ struct Methods {
no_adjust: bool,
}
struct Proto {
//py_methods: &'static [&'static str],
methods: &'static [MethodProto],
}
static DEFAULT_METHODS: Methods = Methods {
methods: &[],
non_pyobj_result: &[],
@ -42,12 +48,6 @@ static DESCR_METHODS: Methods = Methods {
no_adjust: true,
};
static MAPPING_METHODS: Methods = Methods {
methods: &[],
non_pyobj_result: &["__setitem__", "__len__"],
no_adjust: false,
};
static NUM_METHODS: Methods = Methods {
methods: &[
"__radd__", "__rsub__", "__rmul__", "__rmatmul__", "__rtruediv__",
@ -59,6 +59,28 @@ static NUM_METHODS: Methods = Methods {
no_adjust: true,
};
static MAPPING: Proto = Proto {
//py_methods: &[],
methods: &[
MethodProto::Len{
name: "__len__",
proto: "class::mapping::PyMappingLenProtocol"},
MethodProto::Binary{
name: "__getitem__",
arg: "Key",
proto: "class::mapping::PyMappingGetItemProtocol"},
MethodProto::Ternary{
name: "__setitem__",
arg1: "Key",
arg2: "Value",
proto: "class::mapping::PyMappingSetItemProtocol"},
MethodProto::Binary{
name: "__delitem__",
arg: "Key",
proto: "class::mapping::PyMappingDelItemProtocol"},
],
};
enum ImplType {
Object,
@ -95,15 +117,14 @@ pub fn build_py_proto(ast: &mut syn::Item) -> Tokens {
ImplType::GC =>
impl_protocol("pyo3::class::gc::PyGCProtocolImpl",
path.clone(), ty, impl_items, &GC_METHODS),
ImplType::Mapping =>
impl_protocol("pyo3::class::mapping::PyMappingProtocolImpl",
path.clone(), ty, impl_items, &MAPPING_METHODS),
ImplType::Sequence =>
impl_protocol("pyo3::class::mapping::PySequenceProtocolImpl",
path.clone(), ty, impl_items, &DEFAULT_METHODS),
ImplType::Number =>
impl_protocol("pyo3::class::number::PyNumberProtocolImpl",
path.clone(), ty, impl_items, &NUM_METHODS),
ImplType::Mapping =>
impl_proto_impl(ty, impl_items, &MAPPING),
}
} else {
panic!("#[proto] can only be used with protocol trait implementations")
@ -132,6 +153,24 @@ fn process_path(path: &syn::Path) -> ImplType {
}
}
fn impl_proto_impl(ty: &Box<syn::Ty>, impls: &mut Vec<syn::ImplItem>, proto: &Proto) -> Tokens {
let mut tokens = Tokens::new();
for iimpl in impls.iter_mut() {
match iimpl.node {
syn::ImplItemKind::Method(ref mut sig, _) => {
for m in proto.methods {
if m.eq(iimpl.ident.as_ref()) {
impl_method_proto(ty, sig, m).to_tokens(&mut tokens);
}
}
},
_ => (),
}
}
tokens
}
fn impl_protocol(name: &'static str,
path: syn::Path, ty: &Box<syn::Ty>,
impls: &mut Vec<syn::ImplItem>, methods: &Methods) -> Tokens {

View file

@ -42,6 +42,25 @@ macro_rules! py_unary_func {
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_len_func_ {
($trait:ident, $class:ident :: $f:ident, $conv:expr) => {{
unsafe extern "C" fn wrap<T>(slf: *mut $crate::ffi::PyObject) -> $crate::ffi::Py_ssize_t
where T: $trait
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::callback::handle_callback(LOCATION, $conv, |py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<T>();
let ret = slf.$f(py);
$crate::PyDrop::release_ref(slf, py);
ret.into()
})
}
Some(wrap::<$class>)
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_binary_func {
@ -65,6 +84,32 @@ macro_rules! py_binary_func {
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_binary_func_ {
($trait:ident, $class:ident :: $f:ident, $conv:expr) => {{
unsafe extern "C" fn wrap<T>(slf: *mut $crate::ffi::PyObject,
arg: *mut $crate::ffi::PyObject)
-> *mut $crate::ffi::PyObject
where T: $trait
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::callback::handle_callback(LOCATION, $conv, |py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<T>();
let arg = $crate::PyObject::from_borrowed_ptr(py, arg);
let ret = match arg.extract(py) {
Ok(arg) => slf.$f(py, arg).into(),
Err(e) => Err(e),
};
$crate::PyDrop::release_ref(arg, py);
$crate::PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap::<$class>)
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_ternary_func {

View file

@ -9,118 +9,276 @@ use ffi;
use err::{PyErr, PyResult};
use python::{Python, PythonObject, PyDrop};
use objects::{exc, PyObject};
use callback::{handle_callback, PyObjectCallbackConverter,
LenResultConverter, UnitCallbackConverter};
use class::NO_METHODS;
use callback::{PyObjectCallbackConverter, LenResultConverter, UnitCallbackConverter};
use conversion::{ToPyObject, FromPyObject};
/// Mapping interface
pub trait PyMappingProtocol {
fn __len__(&self, py: Python) -> PyResult<usize>;
#[allow(unused_variables)]
pub trait PyMappingProtocol: PythonObject {
fn __len__(&self, py: Python) -> Self::Result
where Self: PyMappingLenProtocol
{ unimplemented!() }
fn __getitem__(&self, py: Python, key: &PyObject) -> PyResult<PyObject>;
fn __getitem__(&self, py: Python, key: Self::Key) -> Self::Result
where Self: PyMappingGetItemProtocol
{ unimplemented!() }
fn __setitem__(&self, py: Python, key: &PyObject, value: &PyObject) -> PyResult<()>;
fn __setitem__(&self, py: Python, key: Self::Key, value: Self::Value) -> Self::Result
where Self: PyMappingSetItemProtocol
{ unimplemented!() }
fn __delitem__(&self, py: Python, key: Self::Key) -> Self::Result
where Self: PyMappingDelItemProtocol
{ unimplemented!() }
fn __delitem__(&self, py: Python, key: &PyObject) -> PyResult<()>;
}
impl<T> PyMappingProtocol for T where T: PythonObject {
default fn __len__(&self, _py: Python) -> PyResult<usize> {
Ok(0)
}
default fn __getitem__(&self, py: Python, _: &PyObject) -> PyResult<PyObject> {
Ok(py.None())
}
// The following are a bunch of marker traits used to detect
// the existance of a slotted method.
default fn __setitem__(&self, py: Python, _: &PyObject, _: &PyObject) -> PyResult<()> {
Err(PyErr::new::<exc::NotImplementedError, _>(
py, format!("Subscript assignment not supported by {:?}", self.as_object())))
}
pub trait PyMappingLenProtocol: PyMappingProtocol {
type Result: Into<PyResult<usize>>;
}
default fn __delitem__(&self, py: Python, _: &PyObject) -> PyResult<()> {
Err(PyErr::new::<exc::NotImplementedError, _>(
py, format!("Subscript deletion not supported by {:?}", self.as_object())))
}
pub trait PyMappingGetItemProtocol: PyMappingProtocol {
type Key: for<'a> FromPyObject<'a>;
type Success: ToPyObject;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyMappingSetItemProtocol: PyMappingProtocol {
type Key: for<'a> FromPyObject<'a>;
type Value: for<'a> FromPyObject<'a>;
type Success: ToPyObject;
type Result: Into<PyResult<()>>;
}
pub trait PyMappingDelItemProtocol: PyMappingProtocol {
type Key: for<'a> FromPyObject<'a>;
type Success: ToPyObject;
type Result: Into<PyResult<()>>;
}
#[doc(hidden)]
pub trait PyMappingProtocolImpl {
fn methods() -> &'static [&'static str];
fn tp_as_mapping() -> Option<ffi::PyMappingMethods>;
}
impl<T> PyMappingProtocolImpl for T {
default fn methods() -> &'static [&'static str] {
NO_METHODS
#[inline]
default fn tp_as_mapping() -> Option<ffi::PyMappingMethods> {
None
}
}
impl ffi::PyMappingMethods {
impl<T> PyMappingProtocolImpl for T where T: PyMappingProtocol {
#[inline]
fn tp_as_mapping() -> Option<ffi::PyMappingMethods> {
let mut f = Self::mp_ass_subscript();
/// Construct PyMappingMethods struct for PyTypeObject.tp_as_mapping
pub fn new<T>() -> Option<ffi::PyMappingMethods>
where T: PyMappingProtocol + PyMappingProtocolImpl + PythonObject
{
let methods = T::methods();
if methods.is_empty() {
return None
if let Some(df) = Self::mp_del_subscript() {
f = Some(df)
}
let mut meth: ffi::PyMappingMethods = ffi::PyMappingMethods_INIT;
for name in methods {
match name {
&"__len__" => {
meth.mp_length = py_len_func!(
PyMappingProtocol, T::__len__, LenResultConverter);
},
&"__getitem__" => {
meth.mp_subscript = py_binary_func!(
PyMappingProtocol, T::__getitem__, PyObjectCallbackConverter);
},
_ => unreachable!(),
}
}
// always set
meth.mp_ass_subscript = Some(mp_ass_subscript::<T>());
Some(meth)
Some(ffi::PyMappingMethods {
mp_length: Self::mp_length(),
mp_subscript: Self::mp_subscript(),
mp_ass_subscript: f,
})
}
}
trait PyMappingLenProtocolImpl {
fn mp_length() -> Option<ffi::lenfunc>;
}
fn mp_ass_subscript<T>() -> ffi::objobjargproc
where T: PyMappingProtocol + PythonObject
impl<T> PyMappingLenProtocolImpl for T
where T: PyMappingProtocol
{
unsafe extern "C" fn wrap<T>(slf: *mut ffi::PyObject,
key: *mut ffi::PyObject,
value: *mut ffi::PyObject) -> c_int
where T: PyMappingProtocol + PythonObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".__setitem__()");
#[inline]
default fn mp_length() -> Option<ffi::lenfunc> {
None
}
}
handle_callback(
LOCATION, UnitCallbackConverter, |py|
{
impl<T> PyMappingLenProtocolImpl for T
where T: PyMappingLenProtocol
{
#[inline]
fn mp_length() -> Option<ffi::lenfunc> {
py_len_func_!(PyMappingLenProtocol, T::__len__, LenResultConverter)
}
}
trait PyMappingGetItemProtocolImpl {
fn mp_subscript() -> Option<ffi::binaryfunc>;
}
impl<T> PyMappingGetItemProtocolImpl for T
where T: PyMappingProtocol
{
#[inline]
default fn mp_subscript() -> Option<ffi::binaryfunc> {
None
}
}
impl<T> PyMappingGetItemProtocolImpl for T
where T: PyMappingGetItemProtocol
{
#[inline]
fn mp_subscript() -> Option<ffi::binaryfunc> {
py_binary_func_!(PyMappingGetItemProtocol, T::__getitem__, PyObjectCallbackConverter)
}
}
trait PyMappingSetItemProtocolImpl {
fn mp_ass_subscript() -> Option<ffi::objobjargproc>;
}
impl<T> PyMappingSetItemProtocolImpl for T
where T: PyMappingProtocol
{
#[inline]
default fn mp_ass_subscript() -> Option<ffi::objobjargproc> {
None
}
}
impl<T> PyMappingSetItemProtocolImpl for T
where T: PyMappingSetItemProtocol
{
#[inline]
fn mp_ass_subscript() -> Option<ffi::objobjargproc> {
unsafe extern "C" fn wrap<T>(slf: *mut ffi::PyObject,
key: *mut ffi::PyObject,
value: *mut ffi::PyObject) -> c_int
where T: PyMappingSetItemProtocol
{
const LOCATION: &'static str = "T.__setitem__()";
::callback::handle_callback(LOCATION, UnitCallbackConverter, |py| {
let slf = PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<T>();
let key = PyObject::from_borrowed_ptr(py, key);
// if value is none, then __delitem__
let ret = if value.is_null() {
slf.__delitem__(py, &key)
} else {
let value = PyObject::from_borrowed_ptr(py, value);
let ret = slf.__setitem__(py, &key, &value);
PyDrop::release_ref(value, py);
ret
let ret = match key.extract(py) {
Ok(key) =>
if value.is_null() {
Err(PyErr::new::<exc::NotImplementedError, _>(
py, format!("Subscript deletion not supported by {:?}",
stringify!(T))))
} else {
let value = PyObject::from_borrowed_ptr(py, value);
let ret = match value.extract(py) {
Ok(value) => slf.__setitem__(py, key, value).into(),
Err(e) => Err(e),
};
PyDrop::release_ref(value, py);
ret
},
Err(e) => Err(e),
};
PyDrop::release_ref(key, py);
PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap::<T>)
}
}
trait PyMappingDelItemProtocolImpl {
fn mp_del_subscript() -> Option<ffi::objobjargproc>;
}
impl<T> PyMappingDelItemProtocolImpl for T
where T: PyMappingProtocol
{
#[inline]
default fn mp_del_subscript() -> Option<ffi::objobjargproc> {
None
}
}
impl<T> PyMappingDelItemProtocolImpl for T
where T: PyMappingDelItemProtocol
{
#[inline]
default fn mp_del_subscript() -> Option<ffi::objobjargproc> {
unsafe extern "C" fn wrap<T>(slf: *mut ffi::PyObject,
key: *mut ffi::PyObject,
value: *mut ffi::PyObject) -> c_int
where T: PyMappingDelItemProtocol
{
const LOCATION: &'static str = "T.__detitem__()";
::callback::handle_callback(LOCATION, UnitCallbackConverter, |py| {
let slf = PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<T>();
let key = PyObject::from_borrowed_ptr(py, key);
let ret = match key.extract(py) {
Ok(key) =>
if value.is_null() {
slf.__delitem__(py, key).into()
} else {
Err(PyErr::new::<exc::NotImplementedError, _>(
py, format!("Subscript assignment not supported by {:?}",
stringify!(T))))
},
Err(e) => Err(e),
};
PyDrop::release_ref(key, py);
PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap::<T>)
}
}
impl<T> PyMappingDelItemProtocolImpl for T
where T: PyMappingSetItemProtocol + PyMappingDelItemProtocol
{
#[inline]
fn mp_del_subscript() -> Option<ffi::objobjargproc> {
unsafe extern "C" fn wrap<T>(slf: *mut ffi::PyObject,
key: *mut ffi::PyObject,
value: *mut ffi::PyObject) -> c_int
where T: PyMappingSetItemProtocol + PyMappingDelItemProtocol
{
const LOCATION: &'static str = "T.__set/del_item__()";
::callback::handle_callback(LOCATION, UnitCallbackConverter, |py| {
let slf = PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<T>();
let key = PyObject::from_borrowed_ptr(py, key);
let ret = if value.is_null() {
match key.extract(py) {
Ok(key) => slf.__delitem__(py, key).into(),
Err(e) => Err(e)
}
} else {
match key.extract(py) {
Ok(key) => {
let value = PyObject::from_borrowed_ptr(py, value);
let ret = match value.extract(py) {
Ok(value) => slf.__setitem__(py, key, value).into(),
Err(e) => Err(e),
};
PyDrop::release_ref(value, py);
ret
},
Err(e) => Err(e),
}
};
PyDrop::release_ref(key, py);
PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap::<T>)
}
wrap::<T>
}

View file

@ -58,7 +58,7 @@ pub fn initialize_type<T>(py: Python, module_name: Option<&str>,
}
// mapping methods
if let Some(meth) = ffi::PyMappingMethods::new::<T>() {
if let Some(meth) = <T as class::mapping::PyMappingProtocolImpl>::tp_as_mapping() {
static mut MP_METHODS: ffi::PyMappingMethods = ffi::PyMappingMethods_INIT;
*(unsafe { &mut MP_METHODS }) = meth;
type_object.tp_as_mapping = unsafe { &mut MP_METHODS };