drop py_class

This commit is contained in:
Nikolay Kim 2017-05-16 23:43:39 -07:00
parent 34e4d956f1
commit c9aefd7e5f
26 changed files with 618 additions and 4229 deletions

View File

@ -7,7 +7,6 @@ readme = "README.md"
keywords = [
"pyo3",
"python",
"cpython",
]
homepage = "https://github.com/pyo3/pyo3"
repository = "https://github.com/pyo3/pyo3.git"

View File

@ -23,10 +23,7 @@ CARGO_FLAGS := --features "$(FEATURES)" --no-default-features
default: test
src/py_class/py_class_impl.rs: src/py_class/py_class_impl.py
PY=3 python $< >$@
build: src/py_class/py_class_impl.rs
build:
cargo build $(CARGO_FLAGS)
test: build

View File

@ -5,16 +5,36 @@ use quote::{Tokens, ToTokens};
pub fn build_py_class(ast: &mut syn::DeriveInput) -> Tokens {
if let syn::Body::Enum(_) = ast.body {
panic!("#[py_class] can only be used with structs")
}
let base = syn::Ident::from("pyo3::PyObject");
let mut tokens = Tokens::new();
match ast.body {
syn::Body::Struct(syn::VariantData::Struct(ref mut data)) => {
impl_storage(&ast.ident, &base, data).to_tokens(&mut tokens);
let tt = quote! {
struct Test {
_unsafe_inner: PyObject
}
};
let t = syn::parse_item(tt.as_str()).unwrap();
match t.node {
syn::ItemKind::Struct(syn::VariantData::Struct(fields), _) => {
data.clear();
data.extend(fields);
}
_ => panic!("something is worng"),
}
},
_ =>
panic!("#[class] can only be used with notmal structs"),
}
impl_to_py_object(&ast.ident).to_tokens(&mut tokens);
impl_from_py_object(&ast.ident).to_tokens(&mut tokens);
impl_python_object(&ast.ident).to_tokens(&mut tokens);
impl_checked_downcast(&ast.ident).to_tokens(&mut tokens);
impl_class_init(&ast.ident).to_tokens(&mut tokens);
let dummy_const = syn::Ident::new(format!("_IMPL_PYO3_CLS_{}", ast.ident));
quote! {
@ -23,13 +43,140 @@ pub fn build_py_class(ast: &mut syn::DeriveInput) -> Tokens {
const #dummy_const: () = {
extern crate pyo3;
use std;
use pyo3::ffi;
use pyo3::class::BaseObject;
use pyo3::{ffi, Python, PyObject, PyType, PyResult, PyModule};
#tokens
};
}
}
fn impl_storage(cls: &syn::Ident, base: &syn::Ident, fields: &Vec<syn::Field>) -> Tokens {
let names: Vec<syn::Ident> = fields.iter()
.map(|f| f.ident.as_ref().unwrap().clone()).collect();
let values: Vec<syn::Ident> = fields.iter()
.map(|f| f.ident.as_ref().unwrap().clone()).collect();
//let types: Vec<syn::Ty> = fields.iter().map(|f| f.ty.clone()).collect();
let mut accessors = Tokens::new();
for field in fields.iter() {
let name = &field.ident.as_ref().unwrap();
let name_mut = syn::Ident::from(format!("{}_mut", name.as_ref()));
let ty = &field.ty;
let accessor = quote!{
impl #cls {
fn #name<'a>(&'a self, py: Python<'a>) -> &'a #ty {
unsafe {
let ptr = (self._unsafe_inner.as_ptr() as *const u8)
.offset(base_offset() as isize) as *const Storage;
&(*ptr).#name
}
}
fn #name_mut<'a>(&'a self, py: Python<'a>) -> &'a mut #ty {
unsafe {
let ptr = (self._unsafe_inner.as_ptr() as *const u8)
.offset(base_offset() as isize) as *mut Storage;
&mut (*ptr).#name
}
}
}
};
accessor.to_tokens(&mut accessors);
}
quote! {
struct Storage {
#(#fields),*
}
impl #cls {
fn create_instance(py: Python, #(#fields),*) -> PyResult<#cls> {
let obj = try!(unsafe {
<#cls as BaseObject>::alloc(
py, &py.get_type::<#cls>(),
Storage { #(#names: #values),*})});
return Ok(#cls { _unsafe_inner: obj });
}
}
#accessors
impl pyo3::PythonObjectWithTypeObject for #cls {
#[inline]
fn type_object(py: Python) -> PyType {
unsafe { #cls::initialized(py, None) }
}
}
impl pyo3::class::PyTypeObject for #cls {
fn add_to_module(py: Python, module: &PyModule) -> PyResult<()> {
let ty = unsafe { #cls::initialized(py, module.name(py).ok()) };
module.add(py, stringify!(#cls), ty)
}
#[inline]
unsafe fn type_obj() -> &'static mut ffi::PyTypeObject {
static mut TYPE_OBJECT: ffi::PyTypeObject = ffi::PyTypeObject_INIT;
&mut TYPE_OBJECT
}
unsafe fn initialized(py: Python, module_name: Option<&str>) -> PyType {
let mut ty = #cls::type_obj();
if (ty.tp_flags & ffi::Py_TPFLAGS_READY) != 0 {
PyType::from_type_ptr(py, ty)
} else {
// automatically initialize the class on-demand
pyo3::class::typeob::initialize_type::<#cls>(
py, module_name, ty).expect(
concat!("An error occurred while initializing class ",
stringify!(#cls)));
PyType::from_type_ptr(py, ty)
}
}
}
#[inline]
fn base_offset() -> usize {
let align = std::mem::align_of::<Storage>();
let bs = <#base as BaseObject>::size();
// round base_size up to next multiple of align
(bs + align - 1) / align * align
}
impl BaseObject for #cls {
type Type = Storage;
#[inline]
fn size() -> usize {
base_offset() + std::mem::size_of::<Self::Type>()
}
unsafe fn alloc(py: Python, ty: &PyType, value: Self::Type) -> PyResult<PyObject>
{
let obj = try!(<#base as BaseObject>::alloc(py, ty, ()));
let ptr = (obj.as_ptr() as *mut u8)
.offset(base_offset() as isize) as *mut Self::Type;
std::ptr::write(ptr, value);
Ok(obj)
}
unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject) {
let ptr = (obj as *mut u8).offset(base_offset() as isize) as *mut Self::Type;
std::ptr::drop_in_place(ptr);
<#base as BaseObject>::dealloc(py, obj)
}
}
}
}
fn impl_to_py_object(cls: &syn::Ident) -> Tokens {
quote! {
/// Identity conversion: allows using existing `PyObject` instances where
@ -130,13 +277,3 @@ fn impl_checked_downcast(cls: &syn::Ident) -> Tokens {
}
}
}
fn impl_class_init(cls: &syn::Ident) -> Tokens {
quote! {
impl pyo3::class::typeob::PyClassInit for #cls {
fn init() -> bool {
true
}
}
}
}

View File

@ -220,8 +220,8 @@ fn impl_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, args: Vec<Arg>) -> Tokens {
{
const LOCATION: &'static str = concat!(
stringify!(#cls), ".", stringify!(#name), "()");
pyo3::_detail::handle_callback(
LOCATION, pyo3::_detail::PyObjectCallbackConverter, |py|
pyo3::callback::handle_callback(
LOCATION, pyo3::callback::PyObjectCallbackConverter, |py|
{
let args: pyo3::PyTuple =
pyo3::PyObject::from_borrowed_ptr(py, args).unchecked_cast_into();
@ -248,8 +248,8 @@ fn impl_wrap_getter(cls: &Box<syn::Ty>, name: &syn::Ident, _args: Vec<Arg>) -> T
{
const LOCATION: &'static str = concat!(
stringify!(#cls), ".getter_", stringify!(#name), "()");
pyo3::_detail::handle_callback(
LOCATION, pyo3::_detail::PyObjectCallbackConverter, |py|
pyo3::callback::handle_callback(
LOCATION, pyo3::callback::PyObjectCallbackConverter, |py|
{
let slf = pyo3::PyObject::from_borrowed_ptr(
py, slf).unchecked_cast_into::<#cls>();
@ -270,8 +270,8 @@ fn impl_wrap_setter(cls: &Box<syn::Ty>, name: &syn::Ident, _args: Vec<Arg>) -> T
{
const LOCATION: &'static str = concat!(
stringify!(#cls), ".setter", stringify!(#name), "()");
pyo3::_detail::handle_callback(
LOCATION, pyo3::py_class::slots::UnitCallbackConverter, |py|
pyo3::callback::handle_callback(
LOCATION, pyo3::callback::UnitCallbackConverter, |py|
{
let slf = pyo3::PyObject::from_borrowed_ptr(py, slf)
.unchecked_cast_into::<#cls>();

242
src/callback.rs Normal file
View File

@ -0,0 +1,242 @@
/// Utilities for a Python callable object that invokes a Rust function.
use std::os::raw::c_int;
use std::{any, mem, ptr, isize, io, marker, panic};
use libc;
use python::{Python, PythonObject};
use objects::exc;
use conversion::ToPyObject;
use ffi::{self, Py_hash_t};
use err::{PyErr, PyResult};
pub trait CallbackConverter<S> {
type R;
fn convert(S, Python) -> Self::R;
fn error_value() -> Self::R;
}
pub struct PyObjectCallbackConverter;
impl <S> CallbackConverter<S> for PyObjectCallbackConverter
where S: ToPyObject
{
type R = *mut ffi::PyObject;
fn convert(val: S, py: Python) -> *mut ffi::PyObject {
val.into_py_object(py).into_object().steal_ptr()
}
#[inline]
fn error_value() -> *mut ffi::PyObject {
ptr::null_mut()
}
}
pub struct PythonObjectCallbackConverter<T>(pub marker::PhantomData<T>);
impl <T, S> CallbackConverter<S> for PythonObjectCallbackConverter<T>
where T: PythonObject,
S: ToPyObject
{
type R = *mut ffi::PyObject;
fn convert(val: S, py: Python) -> *mut ffi::PyObject {
val.into_py_object(py).into_object().steal_ptr()
}
#[inline]
fn error_value() -> *mut ffi::PyObject {
ptr::null_mut()
}
}
pub struct BoolConverter;
impl CallbackConverter<bool> for BoolConverter {
type R = c_int;
#[inline]
fn convert(val: bool, _py: Python) -> c_int {
val as c_int
}
#[inline]
fn error_value() -> c_int {
-1
}
}
pub struct LenResultConverter;
impl CallbackConverter<usize> for LenResultConverter {
type R = isize;
fn convert(val: usize, py: Python) -> isize {
if val <= (isize::MAX as usize) {
val as isize
} else {
PyErr::new_lazy_init(py.get_type::<exc::OverflowError>(), None).restore(py);
-1
}
}
#[inline]
fn error_value() -> isize {
-1
}
}
pub struct UnitCallbackConverter;
impl CallbackConverter<()> for UnitCallbackConverter {
type R = c_int;
#[inline]
fn convert(_: (), _: Python) -> c_int {
0
}
#[inline]
fn error_value() -> c_int {
-1
}
}
pub struct VoidCallbackConverter;
impl CallbackConverter<()> for VoidCallbackConverter {
type R = ();
#[inline]
fn convert(_: (), _: Python) -> () {
()
}
#[inline]
fn error_value() -> () {
()
}
}
pub struct IterNextResultConverter;
impl <T> CallbackConverter<Option<T>>
for IterNextResultConverter
where T: ToPyObject
{
type R = *mut ffi::PyObject;
fn convert(val: Option<T>, py: Python) -> *mut ffi::PyObject {
match val {
Some(val) => val.into_py_object(py).into_object().steal_ptr(),
None => unsafe {
ffi::PyErr_SetNone(ffi::PyExc_StopIteration);
ptr::null_mut()
}
}
}
#[inline]
fn error_value() -> *mut ffi::PyObject {
ptr::null_mut()
}
}
pub trait WrappingCastTo<T> {
fn wrapping_cast(self) -> T;
}
macro_rules! wrapping_cast {
($from:ty, $to:ty) => {
impl WrappingCastTo<$to> for $from {
#[inline]
fn wrapping_cast(self) -> $to {
self as $to
}
}
}
}
wrapping_cast!(u8, Py_hash_t);
wrapping_cast!(u16, Py_hash_t);
wrapping_cast!(u32, Py_hash_t);
wrapping_cast!(usize, Py_hash_t);
wrapping_cast!(u64, Py_hash_t);
wrapping_cast!(i8, Py_hash_t);
wrapping_cast!(i16, Py_hash_t);
wrapping_cast!(i32, Py_hash_t);
wrapping_cast!(isize, Py_hash_t);
wrapping_cast!(i64, Py_hash_t);
pub struct HashConverter;
impl <T> CallbackConverter<T> for HashConverter
where T: WrappingCastTo<Py_hash_t>
{
type R = Py_hash_t;
#[inline]
fn convert(val: T, _py: Python) -> Py_hash_t {
let hash = val.wrapping_cast();
if hash == -1 {
-2
} else {
hash
}
}
#[inline]
fn error_value() -> Py_hash_t {
-1
}
}
pub unsafe fn handle_callback<F, T, C>(location: &str, _c: C, f: F) -> C::R
where F: FnOnce(Python) -> PyResult<T>,
F: panic::UnwindSafe,
C: CallbackConverter<T>
{
let guard = AbortOnDrop(location);
let ret = panic::catch_unwind(|| {
let py = Python::assume_gil_acquired();
match f(py) {
Ok(val) => {
C::convert(val, py)
}
Err(e) => {
e.restore(py);
C::error_value()
}
}
});
let ret = match ret {
Ok(r) => r,
Err(ref err) => {
handle_panic(Python::assume_gil_acquired(), err);
C::error_value()
}
};
mem::forget(guard);
ret
}
fn handle_panic(_py: Python, _panic: &any::Any) {
let msg = cstr!("Rust panic");
unsafe {
ffi::PyErr_SetString(ffi::PyExc_SystemError, msg.as_ptr());
}
}
pub struct AbortOnDrop<'a>(pub &'a str);
impl <'a> Drop for AbortOnDrop<'a> {
fn drop(&mut self) {
use std::io::Write;
let _ = writeln!(&mut io::stderr(), "Cannot unwind out of {}", self.0);
unsafe { libc::abort() }
}
}

View File

@ -10,7 +10,7 @@ use ffi;
use err::PyResult;
use python::{Python, PythonObject};
use objects::PyObject;
use function::PyObjectCallbackConverter;
use callback::PyObjectCallbackConverter;
use class::NO_METHODS;

View File

@ -11,10 +11,9 @@ use ::{CompareOp, Py_hash_t};
use ffi;
use err::{PyErr, PyResult};
use python::{Python, PythonObject, PyDrop};
use conversion::ToPyObject;
use objects::{exc, PyObject};
use py_class::slots::{HashConverter, UnitCallbackConverter};
use function::{handle_callback, PyObjectCallbackConverter};
use conversion::ToPyObject;
use callback::{handle_callback, PyObjectCallbackConverter, HashConverter, UnitCallbackConverter};
use class::{NO_METHODS, NO_PY_METHODS};
// __new__

View File

@ -11,8 +11,7 @@ use ffi;
use err::PyResult;
use python::{Python, PythonObject};
use objects::PyObject;
use py_class::slots::UnitCallbackConverter;
use function::handle_callback;
use callback::{handle_callback, UnitCallbackConverter};
use class::NO_METHODS;

View File

@ -12,8 +12,7 @@ use err::{PyErr, PyResult};
use python::{Python, PythonObject};
use objects::{exc, PyObject};
use class::{NO_METHODS, NO_PY_METHODS};
use function::PyObjectCallbackConverter;
use py_class::slots::UnitCallbackConverter;
use callback::{PyObjectCallbackConverter, UnitCallbackConverter};
/// Descriptor interface
pub trait PyDescrProtocol {

View File

@ -9,7 +9,7 @@ use std::os::raw::{c_int, c_void};
use ffi;
use python::{Python, PythonObject, PyDrop, ToPythonPointer};
use objects::PyObject;
use function::AbortOnDrop;
use callback::AbortOnDrop;
use class::NO_METHODS;
pub struct PyTraverseError(c_int);

View File

@ -11,7 +11,7 @@ macro_rules! py_unary_func {
where T: $trait + PythonObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(LOCATION, $conv, |py| {
$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);
@ -31,7 +31,7 @@ macro_rules! py_unary_func {
where T: $trait + PythonObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(LOCATION, $conv, |py| {
$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);
@ -52,7 +52,7 @@ macro_rules! py_binary_func {
where T: $trait + PythonObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(LOCATION, $conv, |py| {
$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 = slf.$f(py, &arg);
@ -78,7 +78,7 @@ macro_rules! py_ternary_func {
where T: $trait + PythonObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(LOCATION, $conv, |py| {
$crate::callback::handle_callback(LOCATION, $conv, |py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<T>();
let arg1 = $crate::PyObject::from_borrowed_ptr(py, arg1);
let arg2 = $crate::PyObject::from_borrowed_ptr(py, arg2);
@ -102,7 +102,7 @@ macro_rules! py_ssizearg_func {
where T: $trait + PythonObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(LOCATION, $conv, |py| {
$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, arg as isize);
$crate::PyDrop::release_ref(slf, py);
@ -123,7 +123,7 @@ macro_rules! py_objobj_proc {
where T: $trait + PythonObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(LOCATION, $conv, |py| {
$crate::callback::handle_callback(LOCATION, $conv, |py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<T>();
let arg = PyObject::from_borrowed_ptr(py, arg);
let ret = slf.$f(py, &arg);
@ -148,7 +148,7 @@ macro_rules! py_ternary_slot {
where T: $trait + PythonObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
$crate::callback::handle_callback(
LOCATION, $conv, |py|
{
let slf = $crate::PyObject::from_borrowed_ptr(

View File

@ -9,8 +9,8 @@ use ffi;
use err::{PyErr, PyResult};
use python::{Python, PythonObject, PyDrop};
use objects::{exc, PyObject};
use py_class::slots::{LenResultConverter, UnitCallbackConverter};
use function::{handle_callback, PyObjectCallbackConverter};
use callback::{handle_callback, PyObjectCallbackConverter,
LenResultConverter, UnitCallbackConverter};
use class::NO_METHODS;

View File

@ -1,5 +1,8 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use std::mem;
use std::os::raw::c_void;
#[macro_use] mod macros;
pub mod async;
@ -23,9 +26,77 @@ pub use self::number::PyNumberProtocol;
pub use self::mapping::PyMappingProtocol;
pub use self::sequence::PySequenceProtocol;
pub use self::typeob::PyTypeObject;
pub use self::gc::{PyVisit, PyGCProtocol, PyTraverseError};
pub use self::methods::{PyMethodDef, PyMethodDefType, PyMethodType,
PyGetterDef, PySetterDef};
pub static NO_METHODS: &'static [&'static str] = &[];
pub static NO_PY_METHODS: &'static [PyMethodDefType] = &[];
use ffi;
use err::{self, PyResult};
use objects::{PyObject, PyType};
use python::{Python, PythonObject};
#[derive(Debug)]
pub enum CompareOp {
Lt = ffi::Py_LT as isize,
Le = ffi::Py_LE as isize,
Eq = ffi::Py_EQ as isize,
Ne = ffi::Py_NE as isize,
Gt = ffi::Py_GT as isize,
Ge = ffi::Py_GE as isize
}
/// A PythonObject that is usable as a base type for #[class]
pub trait BaseObject : PythonObject {
/// Gets the size of the object, in bytes.
fn size() -> usize;
type Type;
/// Allocates a new object (usually by calling ty->tp_alloc),
/// and initializes it using init_val.
/// `ty` must be derived from the Self type, and the resulting object
/// must be of type `ty`.
unsafe fn alloc(py: Python, ty: &PyType, val: Self::Type) -> PyResult<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);
}
impl BaseObject for PyObject {
#[inline]
fn size() -> usize {
mem::size_of::<ffi::PyObject>()
}
type Type = ();
unsafe fn alloc(py: Python, ty: &PyType, _init_val: ()) -> PyResult<PyObject> {
let ptr = ffi::PyType_GenericAlloc(ty.as_type_ptr(), 0);
err::result_from_owned_ptr(py, ptr)
}
unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) {
// Unfortunately, there is no PyType_GenericFree, so
// we have to manually un-do the work of PyType_GenericAlloc:
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);
}
}
}

View File

@ -9,8 +9,7 @@ use ffi;
use err::PyResult;
use python::{Python, PythonObject};
use objects::PyObject;
use function::PyObjectCallbackConverter;
use py_class::slots::BoolConverter;
use callback::{BoolConverter, PyObjectCallbackConverter};
use class::{NO_METHODS, NO_PY_METHODS};
use class::basic::{PyObjectProtocol, PyObjectProtocolImpl};

View File

@ -9,8 +9,8 @@ use ffi;
use err::{PyErr, PyResult};
use python::{Python, PythonObject, PyDrop};
use objects::{exc, PyObject};
use py_class::slots::{LenResultConverter, UnitCallbackConverter, BoolConverter};
use function::{handle_callback, PyObjectCallbackConverter};
use callback::{handle_callback, PyObjectCallbackConverter,
LenResultConverter, UnitCallbackConverter, BoolConverter};
use class::NO_METHODS;

View File

@ -4,144 +4,130 @@ use std::mem;
use std::ffi::CString;
use std::collections::HashMap;
use ::{ffi, class, py_class, PyErr, Python, PyResult, PythonObject};
use objects::PyType;
use function::AbortOnDrop;
use class::PyMethodDefType;
use ::{ffi, class, PyErr, Python, PyResult, PythonObject};
use objects::{PyType, PyModule};
use callback::AbortOnDrop;
use class::{BaseObject, PyMethodDefType};
pub trait PyClassInit {
pub trait PyTypeObject : BaseObject + PythonObject {
fn init() -> bool;
fn add_to_module(py: Python, module: &PyModule) -> PyResult<()>;
fn type_object() -> &'static mut ffi::PyTypeObject;
unsafe fn type_obj() -> &'static mut ffi::PyTypeObject;
fn build_type(py: Python,
module_name: Option<&str>,
type_object: &mut ffi::PyTypeObject) -> PyResult<PyType>;
unsafe fn initialized(py: Python, module_name: Option<&str>) -> PyType;
}
impl<T> PyClassInit for T
where T: PythonObject + py_class::BaseObject {
pub fn initialize_type<T>(py: Python, module_name: Option<&str>,
type_object: &mut ffi::PyTypeObject) -> PyResult<PyType>
where T: BaseObject + PythonObject
{
// 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();
default fn init() -> bool { false }
type_object.tp_name = name;
default fn type_object() -> &'static mut ffi::PyTypeObject {
static mut TYPE_OBJECT: ffi::PyTypeObject = ffi::PyTypeObject_INIT;
unsafe {
&mut TYPE_OBJECT
}
// 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 BaseObject>::size() as ffi::Py_ssize_t;
// descriptor protocol
type_object.tp_descr_get = class::descr::get_descrfunc::<T>();
type_object.tp_descr_set = class::descr::set_descrfunc::<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;
}
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();
// 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;
}
type_object.tp_name = name;
// 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;
}
// dealloc
type_object.tp_dealloc = Some(tp_dealloc_callback::<T>);
// 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;
}
// GC support
<T as class::gc::PyGCProtocolImpl>::update_type_object(type_object);
// 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;
}
// type size
type_object.tp_basicsize = <T as py_class::BaseObject>::size() as ffi::Py_ssize_t;
// 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 _;
// descriptor protocol
type_object.tp_descr_get = class::descr::get_descrfunc::<T>();
type_object.tp_descr_set = class::descr::set_descrfunc::<T>();
static mut METHODS: *const ffi::PyMethodDef = 0 as *const _;
*(unsafe { &mut METHODS }) = methods.as_ptr();
}
// 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 };
// 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 {
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))
}
Err(PyErr::fetch(py))
}
}
}
pub unsafe extern "C" fn tp_dealloc_callback<T>(obj: *mut ffi::PyObject)
where T: py_class::BaseObject
unsafe extern "C" fn tp_dealloc_callback<T>(obj: *mut ffi::PyObject) where T: BaseObject
{
let guard = AbortOnDrop("Cannot unwind out of tp_dealloc");
let py = Python::assume_gil_acquired();

View File

@ -100,7 +100,7 @@ macro_rules! py_exception {
#[inline]
fn type_object(py: $crate::Python) -> $crate::PyType {
unsafe {
static mut type_object: *mut $crate::_detail::ffi::PyTypeObject = 0 as *mut $crate::_detail::ffi::PyTypeObject;
static mut type_object: *mut $crate::ffi::PyTypeObject = 0 as *mut $crate::ffi::PyTypeObject;
if type_object.is_null() {
type_object = $crate::PyErr::new_type(

View File

@ -16,30 +16,27 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use libc;
use std::{mem, ptr, io, any, marker};
use std::panic;
use python::{Python, PythonObject};
use std::ptr;
use python::Python;
use objects::PyObject;
use conversion::ToPyObject;
use ffi;
use err::{self, PyResult};
use err;
#[macro_export]
#[doc(hidden)]
macro_rules! py_method_def {
($name: expr, $flags: expr, $wrap: expr) => {{
static mut METHOD_DEF: $crate::_detail::ffi::PyMethodDef = $crate::_detail::ffi::PyMethodDef {
static mut METHOD_DEF: $crate::ffi::PyMethodDef = $crate::ffi::PyMethodDef {
//ml_name: bytes!(stringify!($name), "\0"),
ml_name: 0 as *const $crate::_detail::libc::c_char,
ml_name: 0 as *const $crate::c_char,
ml_meth: None,
ml_flags: $crate::_detail::ffi::METH_VARARGS | $crate::_detail::ffi::METH_KEYWORDS | $flags,
ml_doc: 0 as *const $crate::_detail::libc::c_char
ml_flags: $crate::ffi::METH_VARARGS | $crate:::METH_KEYWORDS | $flags,
ml_doc: 0 as *const $crate::c_char
};
METHOD_DEF.ml_name = concat!($name, "\0").as_ptr() as *const _;
METHOD_DEF.ml_meth = Some(
::std::mem::transmute::<$crate::_detail::ffi::PyCFunctionWithKeywords,
$crate::_detail::ffi::PyCFunction>($wrap)
::std::mem::transmute::<$crate::ffi::PyCFunctionWithKeywords,
$crate::ffi::PyCFunction>($wrap)
);
&mut METHOD_DEF
}}
@ -112,13 +109,13 @@ macro_rules! py_fn_impl {
// Form 1: reference existing function
{ $py:expr, $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ] } => {{
unsafe extern "C" fn wrap(
_slf: *mut $crate::_detail::ffi::PyObject,
args: *mut $crate::_detail::ffi::PyObject,
kwargs: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
_slf: *mut $crate::ffi::PyObject,
args: *mut $crate::ffi::PyObject,
kwargs: *mut $crate::ffi::PyObject)
-> *mut $crate::ffi::PyObject
{
$crate::_detail::handle_callback(
stringify!($f), $crate::_detail::PyObjectCallbackConverter,
$crate::callback::handle_callback(
stringify!($f), $crate::callback::PyObjectCallbackConverter,
|py| {
py_argparse_raw!(py, Some(stringify!($f)), args, kwargs,
[ $( { $pname : $ptype = $detail } )* ]
@ -128,7 +125,7 @@ macro_rules! py_fn_impl {
})
}
unsafe {
$crate::_detail::py_fn_impl($py,
$crate::function::py_fn_impl($py,
py_method_def!(stringify!($f), 0, wrap))
}
}};
@ -139,97 +136,8 @@ macro_rules! py_fn_impl {
}}
}
#[allow(dead_code)]
pub unsafe fn py_fn_impl(py: Python, method_def: *mut ffi::PyMethodDef) -> PyObject {
err::from_owned_ptr_or_panic(py, ffi::PyCFunction_New(method_def, ptr::null_mut()))
}
pub trait CallbackConverter<S> {
type R;
fn convert(S, Python) -> Self::R;
fn error_value() -> Self::R;
}
pub struct PyObjectCallbackConverter;
impl <S> CallbackConverter<S> for PyObjectCallbackConverter
where S: ToPyObject
{
type R = *mut ffi::PyObject;
fn convert(val: S, py: Python) -> *mut ffi::PyObject {
val.into_py_object(py).into_object().steal_ptr()
}
#[inline]
fn error_value() -> *mut ffi::PyObject {
ptr::null_mut()
}
}
pub struct PythonObjectCallbackConverter<T>(pub marker::PhantomData<T>);
impl <T, S> CallbackConverter<S> for PythonObjectCallbackConverter<T>
where T: PythonObject,
S: ToPyObject
{
type R = *mut ffi::PyObject;
fn convert(val: S, py: Python) -> *mut ffi::PyObject {
val.into_py_object(py).into_object().steal_ptr()
}
#[inline]
fn error_value() -> *mut ffi::PyObject {
ptr::null_mut()
}
}
pub unsafe fn handle_callback<F, T, C>(location: &str, _c: C, f: F) -> C::R
where F: FnOnce(Python) -> PyResult<T>,
F: panic::UnwindSafe,
C: CallbackConverter<T>
{
let guard = AbortOnDrop(location);
let ret = panic::catch_unwind(|| {
let py = Python::assume_gil_acquired();
match f(py) {
Ok(val) => {
C::convert(val, py)
}
Err(e) => {
e.restore(py);
C::error_value()
}
}
});
let ret = match ret {
Ok(r) => r,
Err(ref err) => {
handle_panic(Python::assume_gil_acquired(), err);
C::error_value()
}
};
mem::forget(guard);
ret
}
fn handle_panic(_py: Python, _panic: &any::Any) {
let msg = cstr!("Rust panic");
unsafe {
ffi::PyErr_SetString(ffi::PyExc_SystemError, msg.as_ptr());
}
}
pub struct AbortOnDrop<'a>(pub &'a str);
impl <'a> Drop for AbortOnDrop<'a> {
fn drop(&mut self) {
use std::io::Write;
let _ = writeln!(&mut io::stderr(), "Cannot unwind out of {}", self.0);
unsafe { libc::abort() }
}
}
// Tests for this file are in tests/test_function.rs

View File

@ -96,7 +96,7 @@ pub use objects::*;
pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectDowncastError, PythonObjectWithTypeObject, PyClone, PyDrop};
pub use pythonrun::{GILGuard, GILProtected, prepare_freethreaded_python};
pub use conversion::{FromPyObject, RefFromPyObject, ToPyObject, ToPyTuple};
pub use py_class::{CompareOp};
pub use class::{CompareOp};
pub use objectprotocol::{ObjectProtocol};
#[allow(non_camel_case_types)]
@ -142,7 +142,7 @@ macro_rules! py_impl_to_py_object_for_python_object {
#[inline]
fn with_borrowed_ptr<F, R>(&self, _py: $crate::Python, f: F) -> R
where F: FnOnce(*mut $crate::_detail::ffi::PyObject) -> R
where F: FnOnce(*mut $crate::ffi::PyObject) -> R
{
f($crate::PythonObject::as_object(self).as_ptr())
}
@ -170,7 +170,6 @@ macro_rules! py_impl_from_py_object_for_python_object {
}
}
pub mod py_class;
mod python;
mod err;
mod conversion;
@ -182,24 +181,11 @@ mod function;
pub mod buffer;
pub mod class;
pub use class::*;
pub mod callback;
// re-export for simplicity
pub use std::os::raw::*;
/// Private re-exports for macros. Do not use.
#[doc(hidden)]
pub mod _detail {
pub mod ffi {
pub use ::ffi::*;
}
pub mod libc {
pub use std::os::raw::{c_char, c_void, c_int};
}
pub use err::{from_owned_ptr_or_panic, result_from_owned_ptr};
pub use function::{handle_callback, py_fn_impl, AbortOnDrop,
PyObjectCallbackConverter, PythonObjectCallbackConverter};
}
/// Expands to an `extern "C"` function that allows Python to load
/// the rust code as a Python extension module.
///
@ -264,7 +250,7 @@ macro_rules! py_module_init {
fn init($py_id: $crate::Python, $m_id: &$crate::PyModule) -> $crate::PyResult<()> {
$body
}
static mut MODULE_DEF: $crate::ffi::PyModuleDef = $crate::_detail::ffi::PyModuleDef_INIT;
static mut MODULE_DEF: $crate::ffi::PyModuleDef = $crate::ffi::PyModuleDef_INIT;
// We can't convert &'static str to *const c_char within a static initializer,
// so we'll do it here in the module initialization:
MODULE_DEF.m_name = concat!(stringify!($name), "\0").as_ptr() as *const _;
@ -278,7 +264,7 @@ pub unsafe fn py_module_init_impl(
def: *mut ffi::PyModuleDef,
init: fn(Python, &PyModule) -> PyResult<()>) -> *mut ffi::PyObject
{
let guard = function::AbortOnDrop("py_module_init");
let guard = callback::AbortOnDrop("py_module_init");
let py = Python::assume_gil_acquired();
ffi::PyEval_InitThreads();
let module = ffi::PyModule_Create(def);

View File

@ -23,7 +23,7 @@ use python::{Python, PythonObject};
use objectprotocol::ObjectProtocol;
use conversion::{ToPyObject, ToPyTuple};
use objects::{PyObject, PyDict, exc};
use py_class::PythonObjectFromPyClassMacro;
use class::PyTypeObject;
use err::{self, PyResult, PyErr};
use std::ffi::{CStr, CString};
@ -107,11 +107,11 @@ impl PyModule {
/// Adds a new extension type to the module.
///
/// This is a convenience function that initializes the `py_class!()`,
/// This is a convenience function that initializes the `class`,
/// sets `new_type.__module__` to this module's name,
/// and adds the type to this module.
pub fn add_class<'p, T>(&self, py: Python<'p>) -> PyResult<()>
where T: PythonObjectFromPyClassMacro
where T: PyTypeObject
{
T::add_to_module(py, self)
}

View File

@ -1,194 +0,0 @@
// Copyright (c) 2016 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use std::marker;
use python::{Python, PythonObject};
use conversion::ToPyObject;
use objects::PyObject;
use err::{self, PyResult};
use ffi;
/// 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<T> where T: PythonObject {
/// Convert the type member into a python object
/// that can be stored in the type dict.
///
/// Because the member may expect `self` values to be of type `T`,
/// `ty` must be T::type_object() or a derived class.
/// (otherwise the behavior is undefined)
unsafe fn into_descriptor(self, py: Python, ty: *mut ffi::PyTypeObject) -> PyResult<PyObject>;
}
impl <T, S> TypeMember<T> for S where T: PythonObject, S: ToPyObject {
#[inline]
unsafe fn into_descriptor(self, py: Python, _ty: *mut ffi::PyTypeObject) -> PyResult<PyObject> {
Ok(self.into_py_object(py).into_object())
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_init_members {
($class:ident, $py:ident, $type_object: ident, { }) => {{}};
($class:ident, $py:ident, $type_object: ident, { $( $name:ident = $init:expr; )+ }) => {{
let dict = $crate::PyDict::new($py);
$( {
// keep $init out of unsafe block; it might contain user code
let init = $init;
let descriptor = try!(unsafe {
$crate::py_class::members::TypeMember::<$class>::into_descriptor(init, $py, &mut $type_object)
});
try!(dict.set_item($py, stringify!($name), descriptor));
})*
unsafe {
assert!($type_object.tp_dict.is_null());
$type_object.tp_dict = $crate::PythonObject::into_object(dict).steal_ptr();
}
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_instance_method {
($py:ident, $class:ident :: $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]) => {{
unsafe extern "C" fn wrap_instance_method(
slf: *mut $crate::_detail::ffi::PyObject,
args: *mut $crate::_detail::ffi::PyObject,
kwargs: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $crate::_detail::PyObjectCallbackConverter,
|py| {
py_argparse_raw!(py, Some(LOCATION), args, kwargs,
[ $( { $pname : $ptype = $detail } )* ]
{
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
let ret = slf.$f(py $(, $pname )* );
$crate::PyDrop::release_ref(slf, py);
ret
})
})
}
unsafe {
let method_def = py_method_def!(stringify!($f), 0, wrap_instance_method);
$crate::py_class::members::create_instance_method_descriptor::<$class>(method_def)
}
}}
}
pub struct InstanceMethodDescriptor<T>(*mut ffi::PyMethodDef, marker::PhantomData<fn(&T)>);
#[inline]
pub unsafe fn create_instance_method_descriptor<T>(method_def: *mut ffi::PyMethodDef)
-> InstanceMethodDescriptor<T>
{
InstanceMethodDescriptor(method_def, marker::PhantomData)
}
impl <T> TypeMember<T> for InstanceMethodDescriptor<T> where T: PythonObject {
#[inline]
unsafe fn into_descriptor(self, py: Python, ty: *mut ffi::PyTypeObject) -> PyResult<PyObject> {
err::result_from_owned_ptr(py, ffi::PyDescr_NewMethod(ty, self.0))
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_class_method {
($py:ident, $class:ident :: $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]) => {{
unsafe extern "C" fn wrap_class_method(
cls: *mut $crate::_detail::ffi::PyObject,
args: *mut $crate::_detail::ffi::PyObject,
kwargs: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $crate::_detail::PyObjectCallbackConverter,
|py| {
py_argparse_raw!(py, Some(LOCATION), args, kwargs,
[ $( { $pname : $ptype = $detail } )* ]
{
let cls = $crate::PyObject::from_borrowed_ptr(py, cls).unchecked_cast_into::<$crate::PyType>();
let ret = $class::$f(&cls, py $(, $pname )* );
$crate::PyDrop::release_ref(cls, py);
ret
})
})
}
unsafe {
let method_def = py_method_def!(stringify!($f),
$crate::_detail::ffi::METH_CLASS,
wrap_class_method);
$crate::py_class::members::create_class_method_descriptor(method_def)
}
}}
}
pub struct ClassMethodDescriptor(*mut ffi::PyMethodDef);
#[inline]
pub unsafe fn create_class_method_descriptor(method_def: *mut ffi::PyMethodDef)
-> ClassMethodDescriptor
{
ClassMethodDescriptor(method_def)
}
impl <T> TypeMember<T> for ClassMethodDescriptor where T: PythonObject {
#[inline]
unsafe fn into_descriptor(self, py: Python, ty: *mut ffi::PyTypeObject) -> PyResult<PyObject> {
err::result_from_owned_ptr(py, ffi::PyDescr_NewClassMethod(ty, self.0))
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_static_method {
($py:ident, $class:ident :: $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]) => {{
unsafe extern "C" fn wrap_static_method(
_slf: *mut $crate::_detail::ffi::PyObject,
args: *mut $crate::_detail::ffi::PyObject,
kwargs: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $crate::_detail::PyObjectCallbackConverter,
|py| {
py_argparse_raw!(py, Some(LOCATION), args, kwargs,
[ $( { $pname : $ptype = $detail } )* ]
{
$class::$f(py $(, $pname )* )
})
})
}
unsafe {
let method_def = py_method_def!(stringify!($f),
$crate::_detail::ffi::METH_STATIC,
wrap_static_method);
$crate::_detail::py_fn_impl($py, method_def)
}
}}
}

View File

@ -1,148 +0,0 @@
// Copyright (c) 2016 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
mod py_class;
mod py_class_impl;
#[doc(hidden)] pub mod slots;
#[doc(hidden)] pub mod members;
use std::os::raw::c_void;
use std::{mem, ptr};
use python::{self, Python, PythonObject};
use objects::{PyObject, PyType, PyModule};
use err::{self, PyResult};
use ffi;
// TODO: consider moving CompareOp to a different module, so that it isn't exported via two paths
#[derive(Debug)]
pub enum CompareOp {
Lt = ffi::Py_LT as isize,
Le = ffi::Py_LE as isize,
Eq = ffi::Py_EQ as isize,
Ne = ffi::Py_NE as isize,
Gt = ffi::Py_GT as isize,
Ge = ffi::Py_GE as isize
}
/// Trait implemented by the types produced by the `py_class!()` macro.
///
/// This is an unstable implementation detail; do not implement manually!
pub trait PythonObjectFromPyClassMacro : python::PythonObjectWithTypeObject {
/// Initializes the class.
///
/// module_name: the name of the parent module into which the class will be placed.
fn initialize(py: Python, module_name: Option<&str>) -> PyResult<PyType>;
/// Initializes the class and adds it to the module.
fn add_to_module(py: Python, module: &PyModule) -> PyResult<()>;
}
#[inline]
#[doc(hidden)]
pub fn data_offset<T>(base_size: usize) -> usize {
let align = mem::align_of::<T>();
// round base_size up to next multiple of align
(base_size + align - 1) / align * align
}
#[inline]
#[doc(hidden)]
pub fn data_new_size<T>(base_size: usize) -> usize {
data_offset::<T>(base_size) + mem::size_of::<T>()
}
#[inline]
#[doc(hidden)]
pub unsafe fn data_get<'a, T>(_py: Python<'a>, obj: &'a PyObject, offset: usize) -> &'a T {
let ptr = (obj.as_ptr() as *const u8).offset(offset as isize) as *const T;
&*ptr
}
#[inline]
#[doc(hidden)]
pub unsafe fn data_init<'a, T>(_py: Python<'a>, obj: &'a PyObject, offset: usize, value: T)
where T: Send + 'static
{
let ptr = (obj.as_ptr() as *mut u8).offset(offset as isize) as *mut T;
ptr::write(ptr, value)
}
#[inline]
#[doc(hidden)]
pub unsafe fn data_drop<'a, T>(_py: Python<'a>, obj: *mut ffi::PyObject, offset: usize) {
let ptr = (obj as *mut u8).offset(offset as isize) as *mut T;
ptr::drop_in_place(ptr)
}
#[inline]
#[doc(hidden)]
pub fn is_ready(_py: Python, ty: &ffi::PyTypeObject) -> bool {
(ty.tp_flags & ffi::Py_TPFLAGS_READY) != 0
}
/// A PythonObject that is usable as a base type with the `py_class!()` macro.
pub trait BaseObject : PythonObject {
/// Gets the size of the object, in bytes.
fn size() -> usize;
type InitType;
/// Allocates a new object (usually by calling ty->tp_alloc),
/// and initializes it using init_val.
/// `ty` must be derived from the Self type, and the resulting object
/// must be of type `ty`.
unsafe fn alloc(py: Python, ty: &PyType, init_val: Self::InitType) -> PyResult<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);
}
impl BaseObject for PyObject {
#[inline]
fn size() -> usize {
mem::size_of::<ffi::PyObject>()
}
type InitType = ();
unsafe fn alloc(py: Python, ty: &PyType, _init_val: ()) -> PyResult<PyObject> {
let ptr = ffi::PyType_GenericAlloc(ty.as_type_ptr(), 0);
//println!("BaseObject::alloc({:?}) = {:?}", ty.as_type_ptr(), ptr);
err::result_from_owned_ptr(py, ptr)
}
unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) {
//println!("BaseObject::dealloc({:?})", ptr);
// Unfortunately, there is no PyType_GenericFree, so
// we have to manually un-do the work of PyType_GenericAlloc:
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);
}
}
}

View File

@ -1,480 +0,0 @@
// Copyright (c) 2016 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
/**
Defines new python extension class.
A `py_class!` macro invocation generates code that declares a new Python class.
Additionally, it generates a Rust struct of the same name, which allows accessing
instances of that Python class from Rust.
# Syntax
`py_class!(pub class MyType |py| { ... })`
* `pub` makes the generated Rust struct visible outside the current module. It has no effect on the visibility from Python.
* `MyType` is the name of the Python class.
* `py` is an identifier that will be made available as a variable of type `Python`
in all function bodies.
* `{ ... }` is the class body, described in more detail below.
# Example
```
#[macro_use] extern crate pyo3;
use pyo3::{Python, PyResult, PyDict};
py_class!(class MyType |py| {
data number: i32;
def __new__(_cls, arg: i32) -> PyResult<MyType> {
MyType::create_instance(py, arg)
}
def half(&self) -> PyResult<i32> {
println!("half() was called with self={:?}", self.number(py));
Ok(self.number(py) / 2)
}
});
fn main() {
let gil = Python::acquire_gil();
let py = gil.python();
let dict = PyDict::new(py);
dict.set_item(py, "MyType", py.get_type::<MyType>()).unwrap();
py.run("assert MyType(42).half() == 21", None, Some(&dict)).unwrap();
}
```
# Generated Rust type
The above example generates the following Rust type:
```ignore
struct MyType { ... }
impl ToPythonObject for MyType { ... }
impl PythonObject for MyType { ... }
impl PythonObjectWithCheckedDowncast for MyType { ... }
impl PythonObjectWithTypeObject for MyType { ... }
impl PythonObjectFromPyClassMacro for MyType { ... }
impl MyType {
fn create_instance(py: Python, number: i32) -> PyResult<MyType> { ... }
// data accessors
fn number<'a>(&'a self, py: Python<'a>) -> &'a i32 { ... }
// functions callable from python
pub fn __new__(_cls: &PyType, py: Python, arg: i32) -> PyResult<MyType> {
MyType::create_instance(py, arg)
}
pub fn half(&self, py: Python) -> PyResult<i32> {
println!("half() was called with self={:?}", self.number(py));
Ok(self.number(py) / 2)
}
}
```
* The generated type implements a number of traits from the `pyo3` crate.
* The inherent `create_instance` method can create new Python objects
given the values for the data fields.
* Private accessors functions are created for the data fields.
* All functions callable from Python are also exposed as public Rust functions.
* To convert from `MyType` to `PyObject`, use `as_object()` or `into_object()` (from the `PythonObject` trait).
* To convert `PyObject` to `MyType`, use `obj.cast_as::<MyType>(py)` or `obj.cast_into::<MyType>(py)`.
# py_class body
The body of a `py_class!` supports the following definitions:
## Data declarations
`data data_name: data_type;`
Declares a data field within the Python class.
Used to store Rust data directly in the Python object instance.
Because Python code can pass all Python objects to other threads,
`data_type` must be `Send + 'static`.
Because Python object instances can be freely shared (Python has no concept of "ownership"),
data fields cannot be declared as `mut`.
If mutability is required, you have to use interior mutability (`Cell` or `RefCell`).
If data members are used to store references to other Python objects, make sure
to read the section "Garbage Collector Integration".
Data declarations are not accessible from Python.
On the Rust side, data is accessed through the automatically generated accessor functions:
```ignore
impl MyType {
fn data_name<'a>(&'a self, py: Python<'a>) -> &'a data_type { ... }
}
```
## Instance methods
`def method_name(&self, parameter-list) -> PyResult<...> { ... }`
Declares an instance method callable from Python.
* Because Python objects are potentially shared, the self parameter must always
be a shared reference (`&self`).
* For details on `parameter-list`, see the documentation of `py_argparse!()`.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
## Class methods
`@classmethod def method_name(cls, parameter-list) -> PyResult<...> { ... }`
Declares a class method callable from Python.
* The first parameter is the type object of the class on which the method is called.
This may be the type object of a derived class.
* The first parameter implicitly has type `&PyType`. This type must not be explicitly specified.
* For details on `parameter-list`, see the documentation of `py_argparse!()`.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
## Static methods
`@staticmethod def method_name(parameter-list) -> PyResult<...> { ... }`
Declares a static method callable from Python.
* For details on `parameter-list`, see the documentation of `py_argparse!()`.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
## __new__
`def __new__(cls, parameter-list) -> PyResult<...> { ... }`
Declares a constructor method callable from Python.
* If no `__new__` method is declared, object instances can only be created from Rust (via `MyType::create_instance`),
but not from Python.
* The first parameter is the type object of the class to create.
This may be the type object of a derived class declared in Python.
* The first parameter implicitly has type `&PyType`. This type must not be explicitly specified.
* For details on `parameter-list`, see the documentation of `py_argparse!()`.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
Usually, `T` will be `MyType`.
## Garbage Collector Integration
If your type owns references to other python objects, you will need to
integrate with Python's garbage collector so that the GC is aware of
those references.
To do this, implement the special member functions `__traverse__` and `__clear__`.
These correspond to the slots `tp_traverse` and `tp_clear` in the Python C API.
`__traverse__` must call `visit.call()` for each reference to another python object.
`__clear__` must clear out any mutable references to other python objects
(thus breaking reference cycles). Immutable references do not have to be cleared,
as every cycle must contain at least one mutable reference.
Example:
```
#[macro_use] extern crate pyo3;
use std::{mem, cell};
use pyo3::{PyObject, PyDrop};
py_class!(class ClassWithGCSupport |py| {
data obj: cell::RefCell<Option<PyObject>>;
def __traverse__(&self, visit) {
if let Some(ref obj) = *self.obj(py).borrow() {
try!(visit.call(obj))
}
Ok(())
}
def __clear__(&self) {
let old_obj = mem::replace(&mut *self.obj(py).borrow_mut(), None);
// Release reference only after the mutable borrow has expired,
// see Caution note below.
old_obj.release_ref(py);
}
});
# fn main() {}
```
Caution: `__traverse__` may be called by the garbage collector:
* during any python operation that takes a `Python` token as argument
* indirectly from the `PyObject` (or derived type) `Drop` implementation
* if your code releases the GIL, at any time by other threads.
If you are using `RefCell<PyObject>`, you must not perform any of the above
operations while your code holds a mutable borrow, or you may cause the borrow
in `__traverse__` to panic.
This is why the example above uses the `mem::replace`/`release_ref` dance:
`release_ref` (or the implicit `Drop`) can only be called safely in a separate
statement, after the mutable borrow on the `RefCell` has expired.
Note that this restriction applies not only to `__clear__`, but to all methods
that use `RefCell::borrow_mut`.
## Iterator Types
Iterators can be defined using the Python special methods `__iter__` and `__next__`:
* `def __iter__(&self) -> PyResult<impl ToPyObject>`
* `def __next__(&self) -> PyResult<Option<impl ToPyObject>>`
Returning `Ok(None)` from `__next__` indicates that that there are no further items.
Example:
```
#[macro_use] extern crate pyo3;
use std::cell::RefCell;
use pyo3::{PyObject, PyClone, PyResult};
py_class!(class MyIterator |py| {
data iter: RefCell<Box<Iterator<Item=PyObject> + Send>>;
def __iter__(&self) -> PyResult<Self> {
Ok(self.clone_ref(py))
}
def __next__(&self) -> PyResult<Option<PyObject>> {
Ok(self.iter(py).borrow_mut().next())
}
});
# fn main() {}
```
## String Conversions
* `def __repr__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
* `def __str__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
Possible return types for `__str__` and `__repr__` are `PyResult<String>` or `PyResult<PyString>`.
In Python 2.7, Unicode strings returned by `__str__` and `__repr__` will be converted to byte strings
by the Python runtime, which results in an exception if the string contains non-ASCII characters.
* `def __bytes__(&self) -> PyResult<PyBytes>`
On Python 3.x, provides the conversion to `bytes`.
On Python 2.7, `__bytes__` is allowed but has no effect.
* `def __unicode__(&self) -> PyResult<PyUnicode>`
On Python 2.7, provides the conversion to `unicode`.
On Python 3.x, `__unicode__` is allowed but has no effect.
* `def __format__(&self, format_spec: &str) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
Special method that is used by the `format()` builtin and the `str.format()` method.
Possible return types are `PyResult<String>` or `PyResult<PyString>`.
## Comparison operators
* `def __richcmp__(&self, other: impl FromPyObject, op: CompareOp) -> PyResult<impl ToPyObject>`
Overloads Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`).
The `op` argument indicates the comparison operation being performed.
The return type will normally be `PyResult<bool>`, but any Python object can be returned.
If `other` is not of the type specified in the signature, the generated code will
automatically `return NotImplemented`.
* `def __hash__(&self) -> PyResult<impl PrimInt>`
Objects that compare equal must have the same hash value.
The return type must be `PyResult<T>` where `T` is one of Rust's primitive integer types.
## Emulating Container Types
* `def __len__(&self) -> PyResult<usize>`
Called by the built-in Python function `len()`.
* `def __length_hint__(&self) -> PyResult<usize>`
Should return an estimated length for the object.
This method is purely an optimization and is never required for correctness.
`__length_hint__` is new in Python 3.4; older versions will ignore the method.
* `def __getitem__(&self, key: impl FromPyObject) -> PyResult<impl ToPyObject>`
Called by the Python subscript operator `self[key]`.
* `def __setitem__(&self, key: impl FromPyObject, value: impl FromPyObject) -> PyResult<()>`
Called by Python `self[key] = value`.
* `def __delitem__(&self, key: impl FromPyObject) -> PyResult<()>`
Called by Python `del self[key]`.
* `def __reversed__(&self) -> PyResult<impl ToPyObject>`
Called by the `reversed()` built-in.
It should return a new iterator object that iterates over all the objects in the container in reverse order.
* `def __contains__(&self, item: impl FromPyObject) -> PyResult<bool>`
Called by Python `item in self`.
For mapping types, this should consider the keys of the mapping rather than the values
or the key-item pairs.
If extraction of the `item` parameter fails with `TypeError`,
`__contains__` will return `Ok(false)`.
## Arithmetic methods
* `def __add__(lhs, rhs) -> PyResult<impl ToPyObject>`
* `def __sub__(lhs, rhs) -> PyResult<impl ToPyObject>`
* `def __mul__(lhs, rhs) -> PyResult<impl ToPyObject>`
* `def __lshift__(lhs, rhs) -> PyResult<impl ToPyObject>`
* `def __rshift__(lhs, rhs) -> PyResult<impl ToPyObject>`
* `def __and__(lhs, rhs) -> PyResult<impl ToPyObject>`
* `def __xor__(lhs, rhs) -> PyResult<impl ToPyObject>`
* `def __or__(lhs, rhs) -> PyResult<impl ToPyObject>`
The parameters `lhs` and `rhs` must not be given an explicit type.
Within the method bodies, both parameters will implicitly have type `&PyObject`.
There are no separate "reversed" versions of these methods (`__radd__()`, etc.)
Instead, if the first operand cannot perform the operation,
the same method of the second operand is called, with the operands in the same order.
This means that you can't rely on the first parameter of these methods being `self`
or being the correct type, and you should test the types of both operands before deciding what to do.
If you can't handle the combination of types you've been given,
you should return `Ok(py.NotImplemented())`.
* `def __iadd__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __isub__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __imul__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __imatmul__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __itruediv__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __ifloordiv__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __imod__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __ilshift__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __irshift__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __iand__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __ixor__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `def __ior__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
Handles inplace operations if possible, falling back to the non-inplace versions.
These methods must return a new reference! In the common case of returning the
same (mutated) object, you will want to return `Ok(self.clone_ref(py))`.
If you can't handle the combination of types you've been given,
you should return `Ok(py.NotImplemented())`.
## Context Manager
* `def __enter__(&self) -> PyResult<impl ToPyObject>`
* `def __exit__(&self, ty: Option<PyType>, value: PyObject, traceback: PyObject) -> PyResult<bool>`
## Other Special Methods
* `def __bool__(&self) -> PyResult<bool>`
Determines the "truthyness" of the object.
Note that `py_class!` always expects this member to be called `__bool__`,
even on Python 2.7 where the Python spelling was `__nonzero__`.
* `def __call__(&self, parameter-list) -> PyResult<impl ToPyObject>`
For details on `parameter-list`, see the documentation of `py_argparse!()`.
The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
*/
#[macro_export]
macro_rules! py_class {
(class $class:ident |$py: ident| { $( $body:tt )* }) => (
py_class_impl! {
{ $( $body )* }
$class $py
/* info: */ {
/* base_type: */ $crate::PyObject,
/* size: */ <$crate::PyObject as $crate::py_class::BaseObject>::size(),
/* class_visibility: */ {},
/* gc: */ {
/* traverse_proc: */ None,
/* traverse_data: */ [ /*name*/ ]
},
/* data: */ [ /* { offset, name, type } */ ]
// TODO: base type, documentation, ...
}
/* slots: */ {
/* type_slots */ [ /* slot: expr, */ ]
/* as_async */ [ /* slot: expr, */ ]
/* as_number */ [ /* slot: expr, */ ]
/* as_sequence */ [ /* slot: expr, */ ]
/* as_mapping */ [ /* slot: expr, */ ]
/* as_buffer */ [ /* slot: expr, */ ]
/* setitem_delitem */ [
sdi_setitem: {},
sdi_delitem: {},
]
}
/* impls: */ { /* impl body */ }
/* members: */ { /* ident = expr; */ }
/* properties: */ { /* expr; */ }
}
);
(pub class $class:ident |$py: ident| { $( $body:tt )* }) => (
py_class_impl! {
{ $( $body )* }
$class $py
/* info: */ {
/* base_type: */ $crate::PyObject,
/* size: */ <$crate::PyObject as $crate::py_class::BaseObject>::size(),
/* class_visibility: */ {pub},
/* gc: */ {
/* traverse_proc: */ None,
/* traverse_data: */ [ /*name*/ ]
},
/* data: */ [ /* { offset, name, type } */ ]
// TODO: base type, documentation, ...
}
/* slots: */ {
/* type_slots */ [ /* slot: expr, */ ]
/* as_async */ [ /* slot: expr, */ ]
/* as_number */ [ /* slot: expr, */ ]
/* as_sequence */ [ /* slot: expr, */ ]
/* as_mapping */ [ /* slot: expr, */ ]
/* as_buffer */ [ /* slot: expr, */ ]
/* setitem_delitem */ [
sdi_setitem: {},
sdi_delitem: {},
]
}
/* impls: */ { /* impl body */ }
/* members: */ { /* ident = expr; */ }
/* properties: */ { /* expr; */ }
}
);
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_impl_item {
{ $class:ident, $py:ident, $name:ident( $( $selfarg:tt )* )
$res_type:ty; $body:block [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]
} => { py_coerce_item! {
impl $class {
pub fn $name($( $selfarg )* $py: $crate::Python $( , $pname: $ptype )* )
-> $res_type $body
}
}}
}

View File

@ -1,827 +0,0 @@
#!/usr/bin/env python
"""
This python script generates the py_class_impl! macro.
"""
from collections import namedtuple
import sys, os
PY2 = (os.getenv('PY') == '2')
header = '''
// Copyright (c) 2016 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
'''
macro_start = '''
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_impl {
// TT muncher macro. Results are accumulated in $info $slots $impls and $members.
'''
base_case = '''
// Base case: we're done munching and can start producing code:
{ {}
$class:ident $py:ident
/* info: */ {
$base_type:ty,
$size:expr,
{ $( $class_visibility:tt )* },
$gc:tt,
/* data: */ [ $( { $data_offset:expr, $data_name:ident, $data_ty:ty } )* ]
}
$slots:tt { $( $imp:item )* } $members:tt $properties:tt
} => {
py_coerce_item! {
$($class_visibility)* struct $class { _unsafe_inner: $crate::PyObject }
}
py_impl_to_py_object_for_python_object!($class);
py_impl_from_py_object_for_python_object!($class);
impl $crate::PythonObject for $class {
#[inline]
fn as_object(&self) -> &$crate::PyObject {
&self._unsafe_inner
}
#[inline]
fn into_object(self) -> $crate::PyObject {
self._unsafe_inner
}
/// Unchecked downcast from PyObject to Self.
/// Undefined behavior if the input object does not have the expected type.
#[inline]
unsafe fn unchecked_downcast_from(obj: $crate::PyObject) -> Self {
$class { _unsafe_inner: obj }
}
/// Unchecked downcast from PyObject to Self.
/// Undefined behavior if the input object does not have the expected type.
#[inline]
unsafe fn unchecked_downcast_borrow_from<'a>(obj: &'a $crate::PyObject) -> &'a Self {
::std::mem::transmute(obj)
}
}
impl $crate::PythonObjectWithCheckedDowncast for $class {
#[inline]
fn downcast_from<'p>(py: $crate::Python<'p>, obj: $crate::PyObject) -> Result<$class, $crate::PythonObjectDowncastError<'p>> {
if py.get_type::<$class>().is_instance(py, &obj) {
Ok($class { _unsafe_inner: obj })
} else {
Err($crate::PythonObjectDowncastError(py))
}
}
#[inline]
fn downcast_borrow_from<'a, 'p>(py: $crate::Python<'p>, obj: &'a $crate::PyObject) -> Result<&'a $class, $crate::PythonObjectDowncastError<'p>> {
if py.get_type::<$class>().is_instance(py, obj) {
unsafe { Ok(::std::mem::transmute(obj)) }
} else {
Err($crate::PythonObjectDowncastError(py))
}
}
}
py_coerce_item! {
impl $crate::py_class::BaseObject for $class {
type InitType = ( $( $data_ty, )* );
#[inline]
fn size() -> usize {
$size
}
unsafe fn alloc(
py: $crate::Python,
ty: &$crate::PyType,
( $( $data_name, )* ): Self::InitType
) -> $crate::PyResult<$crate::PyObject>
{
let obj = try!(<$base_type as $crate::py_class::BaseObject>::alloc(py, ty, ()));
$( $crate::py_class::data_init::<$data_ty>(py, &obj, $data_offset, $data_name); )*
Ok(obj)
}
unsafe fn dealloc(py: $crate::Python, obj: *mut $crate::_detail::ffi::PyObject) {
$( $crate::py_class::data_drop::<$data_ty>(py, obj, $data_offset); )*
<$base_type as $crate::py_class::BaseObject>::dealloc(py, obj)
}
}
}
$($imp)*
py_coerce_item! {
impl $class {
fn create_instance(py: $crate::Python $( , $data_name : $data_ty )* ) -> $crate::PyResult<$class> {
let obj = try!(unsafe {
<$class as $crate::py_class::BaseObject>::alloc(
py, &py.get_type::<$class>(), ( $($data_name,)* )
)
});
return Ok($class { _unsafe_inner: obj });
// hide statics in create_instance to avoid name conflicts
static mut TYPE_OBJECT : $crate::_detail::ffi::PyTypeObject
= py_class_type_object_static_init!($class, $gc, $slots);
static mut INIT_ACTIVE: bool = false;
// trait implementations that need direct access to TYPE_OBJECT
impl $crate::PythonObjectWithTypeObject for $class {
fn type_object(py: $crate::Python) -> $crate::PyType {
unsafe {
if $crate::py_class::is_ready(py, &TYPE_OBJECT) {
$crate::PyType::from_type_ptr(py, &mut TYPE_OBJECT)
} else {
// automatically initialize the class on-demand
<$class as $crate::py_class::PythonObjectFromPyClassMacro>::initialize(py, None)
.expect(concat!("An error occurred while initializing class ", stringify!($class)))
}
}
}
}
impl $crate::py_class::PythonObjectFromPyClassMacro for $class {
fn initialize(py: $crate::Python, module_name: Option<&str>) -> $crate::PyResult<$crate::PyType> {
unsafe {
if $crate::py_class::is_ready(py, &TYPE_OBJECT) {
return Ok($crate::PyType::from_type_ptr(py, &mut TYPE_OBJECT));
}
assert!(!INIT_ACTIVE,
concat!("Reentrancy detected: already initializing class ",
stringify!($class)));
INIT_ACTIVE = true;
let res = init(py, module_name);
INIT_ACTIVE = false;
res
}
}
fn add_to_module(py: $crate::Python, module: &$crate::PyModule) -> $crate::PyResult<()> {
let ty = <$class as $crate::py_class::PythonObjectFromPyClassMacro>::initialize(py, module.name(py).ok())?;
module.add(py, stringify!($class), ty)
}
}
fn init($py: $crate::Python, module_name: Option<&str>) -> $crate::PyResult<$crate::PyType> {
py_class_type_object_dynamic_init!($class, $py, TYPE_OBJECT, module_name, $slots);
py_class_init_members!($class, $py, TYPE_OBJECT, $members);
py_class_init_properties!($class, $py, TYPE_OBJECT, $properties);
unsafe {
if $crate::_detail::ffi::PyType_Ready(&mut TYPE_OBJECT) == 0 {
Ok($crate::PyType::from_type_ptr($py, &mut TYPE_OBJECT))
} else {
Err($crate::PyErr::fetch($py))
}
}
}
}
}
}
};
{ { property $name:ident { $($body:tt)* } $($tail:tt)* }
$class:ident $py:ident $info:tt $slots:tt { $( $imp:item )* } $members:tt
{ $( $properties:expr; )* }
} => { py_class_impl! {
{ $($tail)* }
$class $py $info $slots { $($imp)* } $members
/* properties: */ {
$( $properties; )*
py_class_property_impl! { { $($body)* } $class $py $name { } };
}
}};
'''
indentation = [' ']
last_char = '\n'
def write(text):
global last_char
for line in text.splitlines(True):
line = line.lstrip(' ')
if len(line.strip()) == 0 and last_char == '\n':
continue
if last_char == '\n':
initial_closing = 0
for c in line:
if c in ']}':
initial_closing += 1
else:
break
if initial_closing:
sys.stdout.write(''.join(indentation[:-initial_closing]))
else:
sys.stdout.write(''.join(indentation))
elif last_char not in ' \n' and len(line) > 0 and line[0] not in ' \n;':
sys.stdout.write(' ')
sys.stdout.write(line)
min_indent_level = len(indentation)
for c in line:
if c in '[{':
if len(indentation) > min_indent_level:
indentation.append('')
else:
indentation.append(' ')
elif c in ']}':
indentation.pop()
if len(indentation) < min_indent_level:
min_indent_level = len(indentation)
last_char = line[-1]
slot_groups = (
('tp', 'type_slots', None),
('am', 'as_async', None),
('nb', 'as_number', None),
('sq', 'as_sequence', None),
('mp', 'as_mapping', None),
('bf', 'as_buffer', None),
('sdi', 'setdelitem', ['sdi_setitem', 'sdi_delitem'])
)
def generate_case(pattern, old_info=None, new_info=None, new_impl=None, new_slots=None, new_members=None):
write('{ { %s $($tail:tt)* }\n' % pattern)
write('$class:ident $py:ident')
if old_info is not None:
write(old_info)
elif new_info is not None:
write('\n/* info: */ {\n')
write('$base_type: ty,\n')
write('$size: expr,\n')
write('$class_visibility: tt,\n')
write('$gc: tt,\n')
write('[ $( $data:tt )* ]\n')
write('}\n')
else:
write('$info:tt')
if new_slots:
write('\n/* slots: */ {\n')
for prefix, group_name, explicit_slots in slot_groups:
if any(s.startswith(prefix) for s, v in new_slots):
if explicit_slots is None:
write('\n/* %s */ [ $( $%s_slot_name:ident : $%s_slot_value:expr, )* ]\n'
% (group_name, prefix, prefix))
else:
write('\n/* %s */ [\n' % group_name)
for slot in explicit_slots:
if any(s == slot for s, v in new_slots):
write('%s: {},\n' % slot)
else:
write('%s: $%s_slot_value:tt,\n' % (slot, slot))
write(']\n')
else:
write('$%s:tt' % group_name)
write('\n}\n')
else:
write('$slots:tt')
if new_impl is not None:
write('\n{ $( $imp:item )* }\n')
else:
write('$impls:tt')
if new_members:
write('\n{ $( $member_name:ident = $member_expr:expr; )* }')
else:
write('$members:tt')
write('$properties:tt')
write('\n} => { py_class_impl! {\n')
write('{ $($tail)* }\n')
write('$class $py')
write(new_info or '$info')
if new_slots:
write('\n/* slots: */ {\n')
for prefix, group_name, explicit_slots in slot_groups:
if any(s.startswith(prefix) for s, v in new_slots):
write('\n/* %s */ [\n' % group_name)
if explicit_slots is None:
write('$( $%s_slot_name : $%s_slot_value, )*\n' % (prefix, prefix))
for s, v in new_slots:
if s.startswith(prefix):
write('%s: %s,\n' % (s, v))
else:
for slot in explicit_slots:
slot_value = next((v for s, v in new_slots if s == slot), None)
if slot_value is None:
write('%s: $%s_slot_value,\n' % (slot, slot))
else:
write('%s: { %s },\n' % (slot, slot_value))
write(']\n')
else:
write('$%s' % group_name)
write('\n}\n')
else:
write('$slots')
if new_impl is not None:
write('\n/* impl: */ {\n')
write('$($imp)*\n')
write(new_impl)
write('\n}\n')
else:
write('$impls')
if new_members:
write('\n/* members: */ {\n')
write('$( $member_name = $member_expr; )*\n')
for name, val in new_members:
write('%s = %s;\n' % (name, val))
write('}')
else:
write('$members')
write('$properties')
write('\n}};\n')
def data_decl():
generate_case('data $data_name:ident : $data_type:ty;',
new_info = '''
/* info: */ {
$base_type,
/* size: */ $crate::py_class::data_new_size::<$data_type>($size),
$class_visibility,
$gc,
/* data: */ [
$($data)*
{
$crate::py_class::data_offset::<$data_type>($size),
$data_name,
$data_type
}
]
}
''',
new_impl='''
impl $class {
fn $data_name<'a>(&'a self, py: $crate::Python<'a>) -> &'a $data_type {
unsafe {
$crate::py_class::data_get::<$data_type>(
py,
&self._unsafe_inner,
$crate::py_class::data_offset::<$data_type>($size)
)
}
}
}
''')
def generate_class_method(special_name=None, decoration='',
slot=None, add_member=False, value_macro=None, value_args=None):
name_pattern = special_name or '$name:ident'
name_use = special_name or '$name'
def impl(with_params):
if with_params:
param_pattern = ', $($p:tt)+'
impl = '''py_argparse_parse_plist_impl!{
py_class_impl_item { $class, $py, %s($cls: &$crate::PyType,) $res_type; { $($body)* } }
[] ($($p)+,)
}''' % name_use
value = 'py_argparse_parse_plist_impl!{%s {%s} [] ($($p)+,)}' \
% (value_macro, value_args)
else:
param_pattern = ''
impl = 'py_class_impl_item! { $class, $py,%s($cls: &$crate::PyType,) $res_type; { $($body)* } [] }' \
% name_use
value = '%s!{%s []}' % (value_macro, value_args)
pattern = '%s def %s ($cls:ident%s) -> $res_type:ty { $( $body:tt )* }' \
% (decoration, name_pattern, param_pattern)
slots = []
if slot is not None:
slots.append((slot, value))
members = []
if add_member:
members.append((name_use, value))
generate_case(pattern, new_impl=impl, new_slots=slots, new_members=members)
impl(False) # without parameters
impl(True) # with parameters
def traverse_and_clear():
generate_case('def __traverse__(&$slf:tt, $visit:ident) $body:block',
old_info = '''
/* info: */ {
$base_type: ty,
$size: expr,
$class_visibility: tt,
/* gc: */ {
/* traverse_proc: */ None,
$traverse_data: tt
},
$datas: tt
}
''',
new_info='''
/* info: */ {
$base_type,
$size,
$class_visibility,
/* gc: */ {
/* traverse_proc: */ $class::__traverse__,
$traverse_data
},
$datas
}
''',
new_impl='''
py_coerce_item!{
impl $class {
fn __traverse__(&$slf,
$py: $crate::Python,
$visit: $crate::py_class::gc::VisitProc)
-> Result<(), $crate::py_class::gc::TraverseError>
$body
}
}
''')
generate_case('def __clear__ (&$slf:ident) $body:block',
new_slots=[('tp_clear', 'py_class_tp_clear!($class)')],
new_impl='''
py_coerce_item!{
impl $class {
fn __clear__(&$slf, $py: $crate::Python) $body
}
}
''')
def generate_instance_method(special_name=None, decoration='',
slot=None, add_member=False, value_macro=None, value_args=None):
name_pattern = special_name or '$name:ident'
name_use = special_name or '$name'
def impl(with_params):
if with_params:
param_pattern = ', $($p:tt)+'
impl = '''py_argparse_parse_plist_impl!{
py_class_impl_item { $class, $py, %s(&$slf,) $res_type; { $($body)* } }
[] ($($p)+,)
}''' % name_use
value = 'py_argparse_parse_plist_impl!{%s {%s} [] ($($p)+,)}' \
% (value_macro, value_args)
else:
param_pattern = ''
impl = 'py_class_impl_item! { $class, $py, %s(&$slf,) $res_type; { $($body)* } [] }' \
% name_use
value = '%s!{%s []}' % (value_macro, value_args)
pattern = '%s def %s (&$slf:ident%s) -> $res_type:ty { $( $body:tt )* }' \
% (decoration, name_pattern, param_pattern)
slots = []
if slot is not None:
slots.append((slot, value))
members = []
if add_member:
members.append((name_use, value))
generate_case(pattern, new_impl=impl, new_slots=slots, new_members=members)
impl(False) # without parameters
impl(True) # with parameters
def static_method():
generate_case(
'@staticmethod def $name:ident ($($p:tt)*) -> $res_type:ty { $( $body:tt )* }',
new_impl='''
py_argparse_parse_plist!{
py_class_impl_item { $class, $py, $name() $res_type; { $($body)* } }
($($p)*)
}
''',
new_members=[('$name', '''
py_argparse_parse_plist!{
py_class_static_method {$py, $class::$name}
($($p)*)
}
''')])
def static_data():
generate_case('static $name:ident = $init:expr;',
new_members=[('$name', '$init')])
macro_end = '''
}
'''
def special_method(decorated_function):
def wrap1(*args, **kwargs):
def wrap2(special_name):
return decorated_function(special_name, *args, **kwargs)
return wrap2
return wrap1
@special_method
def error(special_name, msg):
print('''
{ { def %s $($tail:tt)* } $( $stuff:tt )* } => {
py_error! { "%s" }
};''' % (special_name, msg))
@special_method
def unimplemented(special_name):
return error('%s is not supported by py_class! yet.' % special_name)(special_name)
@special_method
def normal_method(special_name):
pass
@special_method
def special_class_method(special_name, *args, **kwargs):
generate_class_method(special_name=special_name, *args, **kwargs)
class Argument(object):
def __init__(self, name, arg_type=None):
self.name = name
if arg_type is not None:
if arg_type == 'int':
arg_type = '$crate::_detail::libc::c_int'
self.type = arg_type
def get_type(self):
if self.type is not None:
return self.type
else:
return '$%s_type' % self.name
def get_arg_pattern(self):
if self.type is not None:
return ', ${0}:ident '.format(self.name)
else:
return ', ${0}:ident : ${0}_type:ty'.format(self.name)
def get_arg_param_item(self):
return '{{ ${0} : {1} = {{}} }}'.format(self.name, self.get_type())
@special_method
def operator(special_name, slot,
args=(),
wrapper=None,
res_type='PyObject',
res_conv=None,
res_ffi_type='*mut $crate::_detail::ffi::PyObject',
additional_slots=()
):
if res_conv is None:
if res_type == 'void':
res_conv = '$crate::py_class::slots::VoidCallbackConverter'
if res_type == '()':
res_conv = '$crate::py_class::slots::UnitCallbackConverter'
res_ffi_type = '$crate::_detail::libc::c_int'
elif res_type == 'bool':
res_conv = '$crate::py_class::slots::BoolConverter'
res_ffi_type = '$crate::_detail::libc::c_int'
elif res_type == 'PyObject':
res_conv = '$crate::_detail::PyObjectCallbackConverter'
else:
res_conv = '$crate::_detail::PythonObjectCallbackConverter::<$crate::%s>(::std::marker::PhantomData)' % res_type
arg_pattern = ''
param_list = []
for arg in args:
arg_pattern += arg.get_arg_pattern()
param_list.append(arg.get_arg_param_item())
if slot == 'sq_contains':
new_slots = [(slot, 'py_class_contains_slot!($class::%s, $%s_type)' % (special_name, args[0].name))]
elif slot == 'tp_richcompare':
new_slots = [(slot, 'py_class_richcompare_slot!($class::%s, $%s_type, %s, %s)'
% (special_name, args[0].name, res_ffi_type, res_conv))]
elif len(args) == 0:
new_slots = [(slot, 'py_class_unary_slot!($class::%s, %s, %s)'
% (special_name, res_ffi_type, res_conv))]
elif len(args) == 1:
new_slots = [(slot, 'py_class_binary_slot!($class::%s, %s, %s, %s)'
% (special_name, args[0].get_type(), res_ffi_type, res_conv))]
elif len(args) == 2:
if wrapper is None:
wrapper = 'py_class_ternary_slot'
new_slots = [(slot, '%s!($class::%s, %s, %s, %s, %s)'
% (wrapper, special_name, args[0].get_type(), args[1].get_type(), res_ffi_type, res_conv))]
else:
raise ValueError('Unsupported argument count')
generate_case(
pattern='def %s(&$slf:ident%s) -> $res_type:ty { $($body:tt)* }' % (special_name, arg_pattern),
new_impl='py_class_impl_item! { $class, $py, %s(&$slf,) $res_type; { $($body)* } [%s] }'
% (special_name, ' '.join(param_list)),
new_slots=new_slots + list(additional_slots)
)
# Generate fall-back matcher that produces an error
# when using the wrong method signature
error('Invalid signature for operator %s' % special_name)(special_name)
@special_method
def call_operator(special_name, slot):
generate_instance_method(
special_name=special_name,
slot=slot,
value_macro='py_class_call_slot',
value_args='$class::%s' % special_name)
@special_method
def binary_numeric_operator(special_name, slot):
generate_case(
pattern='def %s($left:ident, $right:ident) -> $res_type:ty { $($body:tt)* }'
% special_name,
new_impl='py_class_impl_item! { $class, $py, %s() $res_type; { $($body)* } ' % special_name
+'[ { $left : &$crate::PyObject = {} } { $right : &$crate::PyObject = {} } ] }',
new_slots=[(slot, 'py_class_binary_numeric_slot!($class::%s)' % special_name)]
)
error('Invalid signature for binary numeric operator %s' % special_name)(special_name)
@special_method
def reflected_numeric_operator(special_name):
error('Reflected numeric operator %s is not supported by py_class! Use __%s__ instead!'
% (special_name, special_name[3:-2]))(special_name)
@special_method
def inplace_numeric_operator(special_name, slot):
operator(slot=slot,
args=[Argument('other')])(special_name)
special_names = {
'__init__': error('__init__ is not supported by py_class!; use __new__ instead.'),
'__new__': special_class_method(
slot='tp_new',
value_macro='py_class_wrap_newfunc',
value_args='$class::__new__'),
'__del__': error('__del__ is not supported by py_class!; Use a data member with a Drop impl instead.'),
'__repr__': operator('tp_repr', res_type="PyString"),
'__str__': operator('tp_str', res_type="PyString"),
'__unicode__': normal_method(),
'__bytes__': normal_method(),
'__format__': normal_method(),
# Comparison Operators
'__lt__': error('__lt__ is not supported by py_class! use __richcmp__ instead.'),
'__le__': error('__le__ is not supported by py_class! use __richcmp__ instead.'),
'__gt__': error('__gt__ is not supported by py_class! use __richcmp__ instead.'),
'__ge__': error('__ge__ is not supported by py_class! use __richcmp__ instead.'),
'__eq__': error('__eq__ is not supported by py_class! use __richcmp__ instead.'),
'__ne__': error('__ne__ is not supported by py_class! use __richcmp__ instead.'),
'__cmp__': error('__cmp__ is not supported by py_class! use __richcmp__ instead.'),
'__richcmp__': operator('tp_richcompare',
res_type='PyObject',
args=[Argument('other'), Argument('op')]),
'__hash__': operator('tp_hash',
res_conv='$crate::py_class::slots::HashConverter',
res_ffi_type='$crate::Py_hash_t'),
'__nonzero__': error('__nonzero__ is not supported by py_class!; use the Python 3 spelling __bool__ instead.'),
'__bool__': operator('nb_nonzero' if PY2 else 'nb_bool',
res_type='bool'),
# Customizing attribute access
'__getattr__': unimplemented(),
'__getattribute__': unimplemented(),
'__setattr__': unimplemented(),
'__delattr__': unimplemented(),
'__dir__': unimplemented(),
# Implementing Descriptors
'__get__': unimplemented(),
'__set__': unimplemented(),
'__delete__': unimplemented(),
# Customizing instance and subclass checks
'__instancecheck__': unimplemented(),
'__subclasscheck__': unimplemented(),
# Emulating callable objects
'__call__': call_operator('tp_call'),
# Emulating container types
'__len__': operator('sq_length',
res_ffi_type='$crate::_detail::ffi::Py_ssize_t',
res_conv='$crate::py_class::slots::LenResultConverter',
additional_slots=[
# Use PySequence_Size to forward mp_length calls to sq_length.
('mp_length', 'Some($crate::_detail::ffi::PySequence_Size)')
]),
'__length_hint__': normal_method(),
'__getitem__': operator('mp_subscript',
args=[Argument('key')],
additional_slots=[
('sq_item', 'Some($crate::py_class::slots::sq_item)')
]),
'__missing__': normal_method(),
'__setitem__': operator('sdi_setitem',
args=[Argument('key'), Argument('value')],
res_type='()'),
'__delitem__': operator('sdi_delitem',
args=[Argument('key')],
res_type='()'),
'__iter__': operator('tp_iter'),
'__next__': operator('tp_iternext',
res_conv='$crate::py_class::slots::IterNextResultConverter'),
'__reversed__': normal_method(),
'__contains__': operator('sq_contains', args=[Argument('item')]),
# Emulating numeric types
'__add__': binary_numeric_operator('nb_add'),
'__sub__': binary_numeric_operator('nb_subtract'),
'__mul__': binary_numeric_operator('nb_multiply'),
'__matmul__': unimplemented(),
'__div__': unimplemented(),
'__truediv__': unimplemented(),
'__floordiv__': unimplemented(),
'__mod__': unimplemented(),
'__divmod__': unimplemented(),
'__pow__': unimplemented(),
'__lshift__': binary_numeric_operator('nb_lshift'),
'__rshift__': binary_numeric_operator('nb_rshift'),
'__and__': binary_numeric_operator('nb_and'),
'__xor__': binary_numeric_operator('nb_xor'),
'__or__': binary_numeric_operator('nb_or'),
# Emulating numeric types - reflected
'__radd__': reflected_numeric_operator(),
'__rsub__': reflected_numeric_operator(),
'__rmul__': reflected_numeric_operator(),
'__rmatmul__': reflected_numeric_operator(),
'__rdiv__': reflected_numeric_operator(),
'__rtruediv__': reflected_numeric_operator(),
'__rfloordiv__': reflected_numeric_operator(),
'__rmod__': reflected_numeric_operator(),
'__rdivmod__': reflected_numeric_operator(),
'__rpow__': reflected_numeric_operator(),
'__rlshift__': reflected_numeric_operator(),
'__rrshift__': reflected_numeric_operator(),
'__rand__': reflected_numeric_operator(),
'__rxor__': reflected_numeric_operator(),
'__ror__': reflected_numeric_operator(),
# Emulating numeric types - in-place
'__iadd__': inplace_numeric_operator('nb_inplace_add'),
'__isub__': inplace_numeric_operator('nb_inplace_subtract'),
'__imul__': inplace_numeric_operator('nb_inplace_multiply'),
'__imatmul__': inplace_numeric_operator('nb_inplace_matrix_multiply'),
'__idiv__': unimplemented(),
'__itruediv__': inplace_numeric_operator('nb_inplace_true_divide'),
'__ifloordiv__': inplace_numeric_operator('nb_inplace_floor_divide'),
'__imod__': inplace_numeric_operator('nb_inplace_remainder'),
'__ipow__': unimplemented(),
'__ilshift__': inplace_numeric_operator('nb_inplace_lshift'),
'__irshift__': inplace_numeric_operator('nb_inplace_rshift'),
'__iand__': inplace_numeric_operator('nb_inplace_and'),
'__ixor__': inplace_numeric_operator('nb_inplace_xor'),
'__ior__': inplace_numeric_operator('nb_inplace_or'),
# Unary arithmetic
'__neg__': operator('nb_negative'),
'__pos__': operator('nb_positive'),
'__abs__': operator('nb_absolute'),
'__invert__': operator('nb_invert'),
'__complex__': unimplemented(),
'__int__': unimplemented(),
'__long__': unimplemented(),
'__float__': unimplemented(),
'__round__': unimplemented(),
'__index__': unimplemented(),
'__coerce__': unimplemented(),
# With statement context managers
'__enter__': normal_method(),
'__exit__': normal_method(),
}
def main():
if sys.argv[1:] == ['--format']:
while True:
line = sys.stdin.readline()
if len(line) == 0:
return
write(line)
print(header)
print('')
print('// !!!!!!!!!!!!!!!!!!!!!!!!!!!')
print('// THIS IS A GENERATED FILE !!')
print('// DO NOT MODIFY !!')
print('// !!!!!!!!!!!!!!!!!!!!!!!!!!!')
print(macro_start)
print(base_case)
data_decl()
traverse_and_clear()
for name, f in sorted(special_names.items()):
f(name)
generate_instance_method(
add_member=True,
value_macro='py_class_instance_method',
value_args='$py, $class::$name')
generate_class_method(decoration='@classmethod',
add_member=True,
value_macro='py_class_class_method',
value_args='$py, $class::$name')
static_method()
static_data()
print(macro_end)
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@ -1,531 +0,0 @@
// Copyright (c) 2016 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use ffi;
use std::{isize, ptr};
use std::os::raw::{c_int};
use python::{Python, PythonObject};
use conversion::ToPyObject;
use function::CallbackConverter;
use err::{PyErr, PyResult};
use py_class::{CompareOp};
use exc;
use Py_hash_t;
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_type_object_static_init {
($class_name:ident,
$gc:tt,
/* slots: */ {
/* type_slots */ [ $( $slot_name:ident : $slot_value:expr, )* ]
$as_async:tt
$as_number:tt
$as_sequence:tt
$as_mapping:tt
$as_buffer:tt
$setdelitem:tt
}) => (
$crate::_detail::ffi::PyTypeObject {
$( $slot_name : $slot_value, )*
..
$crate::_detail::ffi::PyTypeObject_INIT
}
);
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_wrap_newfunc {
($class:ident :: $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]) => {{
unsafe extern "C" fn wrap_newfunc(
cls: *mut $crate::_detail::ffi::PyTypeObject,
args: *mut $crate::_detail::ffi::PyObject,
kwargs: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $crate::_detail::PyObjectCallbackConverter,
|py| {
py_argparse_raw!(py, Some(LOCATION), args, kwargs,
[ $( { $pname : $ptype = $detail } )* ]
{
let cls = $crate::PyType::from_type_ptr(py, cls);
let ret = $class::$f(&cls, py $(, $pname )* );
$crate::PyDrop::release_ref(cls, py);
ret
})
})
}
Some(wrap_newfunc)
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_as_number {
([]) => (0 as *mut $crate::_detail::ffi::PyNumberMethods);
([$( $slot_name:ident : $slot_value:expr ,)+]) => {{
static mut NUMBER_METHODS : $crate::_detail::ffi::PyNumberMethods
= $crate::_detail::ffi::PyNumberMethods {
$( $slot_name : $slot_value, )*
..
$crate::_detail::ffi::PyNumberMethods_INIT
};
unsafe { &mut NUMBER_METHODS }
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_unary_slot {
($class:ident :: $f:ident, $res_type:ty, $conv:expr) => {{
unsafe extern "C" fn wrap_unary(
slf: *mut $crate::_detail::ffi::PyObject)
-> $res_type
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $conv,
|py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
let ret = slf.$f(py);
$crate::PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap_unary)
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_binary_slot {
($class:ident :: $f:ident, $arg_type:ty, $res_type:ty, $conv:expr) => {{
unsafe extern "C" fn wrap_binary(
slf: *mut $crate::_detail::ffi::PyObject,
arg: *mut $crate::_detail::ffi::PyObject)
-> $res_type
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $conv,
|py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
let arg = $crate::PyObject::from_borrowed_ptr(py, arg);
let ret = match <$arg_type as $crate::FromPyObject>::extract(py, &arg) {
Ok(arg) => slf.$f(py, arg),
Err(e) => Err(e)
};
$crate::PyDrop::release_ref(arg, py);
$crate::PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap_binary)
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_ternary_slot {
($class:ident :: $f:ident, $arg1_type:ty, $arg2_type:ty, $res_type:ty, $conv:expr) => {{
unsafe extern "C" fn wrap_binary(
slf: *mut $crate::_detail::ffi::PyObject,
arg1: *mut $crate::_detail::ffi::PyObject,
arg2: *mut $crate::_detail::ffi::PyObject)
-> $res_type
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $conv,
|py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
let arg1 = $crate::PyObject::from_borrowed_ptr(py, arg1);
let arg2 = $crate::PyObject::from_borrowed_ptr(py, arg2);
let ret = match <$arg1_type as $crate::FromPyObject>::extract(py, &arg1) {
Ok(arg1) => match <$arg2_type as $crate::FromPyObject>::extract(py, &arg2) {
Ok(arg2) => slf.$f(py, arg1, arg2),
Err(e) => Err(e)
},
Err(e) => Err(e)
};
$crate::PyDrop::release_ref(arg1, py);
$crate::PyDrop::release_ref(arg2, py);
$crate::PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap_binary)
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_binary_internal {
($class:ident :: $f:ident, $arg1_type:ty, $res_type:ty, $conv:expr) => {{
unsafe extern "C" fn wrap_binary(
slf: *mut $crate::_detail::ffi::PyObject,
arg1: $arg1_type)
-> $res_type {
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $conv,
|py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
Ok(slf.$f(py, arg1))
})
}
Some(wrap_binary)
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_ternary_internal {
($class:ident :: $f:ident, $arg1_type:ty, $arg2_type:ty, $res_type:ty, $conv:expr) => {{
unsafe extern "C" fn wrap(
slf: *mut $crate::_detail::ffi::PyObject,
arg1: $arg1_type,
arg2: $arg2_type)
-> $res_type
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $conv,
|py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
Ok(slf.$f(py, arg1, arg2))
})
}
Some(wrap)
}}
}
pub fn extract_op(py: Python, op: c_int) -> PyResult<CompareOp> {
match op {
ffi::Py_LT => Ok(CompareOp::Lt),
ffi::Py_LE => Ok(CompareOp::Le),
ffi::Py_EQ => Ok(CompareOp::Eq),
ffi::Py_NE => Ok(CompareOp::Ne),
ffi::Py_GT => Ok(CompareOp::Gt),
ffi::Py_GE => Ok(CompareOp::Ge),
_ => Err(PyErr::new_lazy_init(
py.get_type::<exc::ValueError>(),
Some("tp_richcompare called with invalid comparison operator".to_py_object(py).into_object())))
}
}
// sq_richcompare is special-cased slot
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_richcompare_slot {
($class:ident :: $f:ident, $arg_type:ty, $res_type:ty, $conv:expr) => {{
unsafe extern "C" fn tp_richcompare(
slf: *mut $crate::_detail::ffi::PyObject,
arg: *mut $crate::_detail::ffi::PyObject,
op: $crate::_detail::libc::c_int)
-> $res_type
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $conv,
|py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
let arg = $crate::PyObject::from_borrowed_ptr(py, arg);
let ret = match $crate::py_class::slots::extract_op(py, op) {
Ok(op) => match <$arg_type as $crate::FromPyObject>::extract(py, &arg) {
Ok(arg) => slf.$f(py, arg, op).map(|res| { res.into_py_object(py).into_object() }),
Err(_) => Ok(py.NotImplemented())
},
Err(_) => Ok(py.NotImplemented())
};
$crate::PyDrop::release_ref(arg, py);
$crate::PyDrop::release_ref(slf, py);
ret
})
}
Some(tp_richcompare)
}}
}
// sq_contains is special-cased slot because it converts type errors to Ok(false)
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_contains_slot {
($class:ident :: $f:ident, $arg_type:ty) => {{
unsafe extern "C" fn sq_contains(
slf: *mut $crate::_detail::ffi::PyObject,
arg: *mut $crate::_detail::ffi::PyObject)
-> $crate::_detail::libc::c_int
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $crate::py_class::slots::BoolConverter,
|py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
let arg = $crate::PyObject::from_borrowed_ptr(py, arg);
let ret = match <$arg_type as $crate::FromPyObject>::extract(py, &arg) {
Ok(arg) => slf.$f(py, arg),
Err(e) => $crate::py_class::slots::type_error_to_false(py, e)
};
$crate::PyDrop::release_ref(arg, py);
$crate::PyDrop::release_ref(slf, py);
ret
})
}
Some(sq_contains)
}}
}
pub fn type_error_to_false(py: Python, e: PyErr) -> PyResult<bool> {
if e.matches(py, py.get_type::<exc::TypeError>()) {
Ok(false)
} else {
Err(e)
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_binary_numeric_slot {
($class:ident :: $f:ident) => {{
unsafe extern "C" fn binary_numeric(
lhs: *mut $crate::_detail::ffi::PyObject,
rhs: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $crate::_detail::PyObjectCallbackConverter,
|py| {
let lhs = $crate::PyObject::from_borrowed_ptr(py, lhs);
let rhs = $crate::PyObject::from_borrowed_ptr(py, rhs);
let ret = $class::$f(py, &lhs, &rhs);
$crate::PyDrop::release_ref(lhs, py);
$crate::PyDrop::release_ref(rhs, py);
ret
})
}
Some(binary_numeric)
}}
}
pub struct VoidCallbackConverter;
impl CallbackConverter<()> for VoidCallbackConverter {
type R = ();
#[inline]
fn convert(_: (), _: Python) -> () {
()
}
#[inline]
fn error_value() -> () {
()
}
}
pub struct UnitCallbackConverter;
impl CallbackConverter<()> for UnitCallbackConverter {
type R = c_int;
#[inline]
fn convert(_: (), _: Python) -> c_int {
0
}
#[inline]
fn error_value() -> c_int {
-1
}
}
pub struct LenResultConverter;
impl CallbackConverter<usize> for LenResultConverter {
type R = isize;
fn convert(val: usize, py: Python) -> isize {
if val <= (isize::MAX as usize) {
val as isize
} else {
PyErr::new_lazy_init(py.get_type::<exc::OverflowError>(), None).restore(py);
-1
}
}
#[inline]
fn error_value() -> isize {
-1
}
}
pub struct IterNextResultConverter;
impl <T> CallbackConverter<Option<T>>
for IterNextResultConverter
where T: ToPyObject
{
type R = *mut ffi::PyObject;
fn convert(val: Option<T>, py: Python) -> *mut ffi::PyObject {
match val {
Some(val) => val.into_py_object(py).into_object().steal_ptr(),
None => unsafe {
ffi::PyErr_SetNone(ffi::PyExc_StopIteration);
ptr::null_mut()
}
}
}
#[inline]
fn error_value() -> *mut ffi::PyObject {
ptr::null_mut()
}
}
pub trait WrappingCastTo<T> {
fn wrapping_cast(self) -> T;
}
macro_rules! wrapping_cast {
($from:ty, $to:ty) => {
impl WrappingCastTo<$to> for $from {
#[inline]
fn wrapping_cast(self) -> $to {
self as $to
}
}
}
}
wrapping_cast!(u8, Py_hash_t);
wrapping_cast!(u16, Py_hash_t);
wrapping_cast!(u32, Py_hash_t);
wrapping_cast!(usize, Py_hash_t);
wrapping_cast!(u64, Py_hash_t);
wrapping_cast!(i8, Py_hash_t);
wrapping_cast!(i16, Py_hash_t);
wrapping_cast!(i32, Py_hash_t);
wrapping_cast!(isize, Py_hash_t);
wrapping_cast!(i64, Py_hash_t);
pub struct HashConverter;
impl <T> CallbackConverter<T> for HashConverter
where T: WrappingCastTo<Py_hash_t>
{
type R = Py_hash_t;
#[inline]
fn convert(val: T, _py: Python) -> Py_hash_t {
let hash = val.wrapping_cast();
if hash == -1 {
-2
} else {
hash
}
}
#[inline]
fn error_value() -> Py_hash_t {
-1
}
}
pub struct BoolConverter;
impl CallbackConverter<bool> for BoolConverter {
type R = c_int;
#[inline]
fn convert(val: bool, _py: Python) -> c_int {
val as c_int
}
#[inline]
fn error_value() -> c_int {
-1
}
}
pub struct SuccessConverter;
impl CallbackConverter<bool> for SuccessConverter {
type R = c_int;
#[inline]
fn convert(val: bool, _py: Python) -> c_int {
if val { 0 } else { -1 }
}
#[inline]
fn error_value() -> c_int {
-1
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_call_slot {
($class:ident :: $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]) => {{
unsafe extern "C" fn wrap_call(
slf: *mut $crate::_detail::ffi::PyObject,
args: *mut $crate::_detail::ffi::PyObject,
kwargs: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $crate::_detail::PyObjectCallbackConverter,
|py| {
py_argparse_raw!(py, Some(LOCATION), args, kwargs,
[ $( { $pname : $ptype = $detail } )* ]
{
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
let ret = slf.$f(py $(, $pname )* );
$crate::PyDrop::release_ref(slf, py);
ret
})
})
}
Some(wrap_call)
}}
}
/// Used as implementation in the `sq_item` slot to forward calls to the `mp_subscript` slot.
pub unsafe extern "C" fn sq_item(obj: *mut ffi::PyObject, index: ffi::Py_ssize_t) -> *mut ffi::PyObject {
let arg = ffi::PyLong_FromSsize_t(index);
if arg.is_null() {
return arg;
}
let ret = ffi::PyObject_GetItem(obj, arg);
ffi::Py_DECREF(arg);
ret
}