diff --git a/.gitignore b/.gitignore index cf119a1d..f332ec3c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ /Cargo.lock /doc /gh-pages +/pyo3cls/target +/pyo3cls/Cargo.lock *.so *.out diff --git a/Cargo.toml b/Cargo.toml index 6f12e171..7cfcfaf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ description = "Bindings to Python" authors = ["PyO3 Project and Contributors Result<(PythonVersion, String, Vec interpreter_version.minor.unwrap_or(0) { + if interpreter_version.major < 3 || MIN_MINOR > interpreter_version.minor.unwrap_or(0) { return Err(format!("Unsupported python version in PYTHON_SYS_EXECUTABLE={}\n\ \tmin version 3.4 != found {}", interpreter_path, @@ -270,8 +270,9 @@ fn find_interpreter_and_get_config() -> Result<(PythonVersion, String, Vec Result<(String, String), String> { let mut flags = String::new(); - println!("test: {:?}", interpreter_version); if let PythonVersion { major: 3, minor: some_minor} = interpreter_version { if env::var_os("CARGO_FEATURE_PEP_384").is_some() { println!("cargo:rustc-cfg=Py_LIMITED_API"); @@ -346,7 +346,6 @@ fn main() { // try using 'env' (sorry but this isn't our fault - it just has to // match the pkg-config package name, which is going to have a . in it). let (python_interpreter_path, flags) = configure_from_path().unwrap(); - println!('3'); let config_map = get_config_vars(&python_interpreter_path).unwrap(); for (key, val) in &config_map { match cfg_line_for_var(key, val) { @@ -380,5 +379,6 @@ fn main() { }) + flags.as_str(); println!("cargo:python_flags={}", - if flags.len() > 0 { &flags[..flags.len()-1] } else { "" }); + if flags.len() > 0 { &flags[..flags.len()-1] } else { "" }); + } diff --git a/pyo3cls/Cargo.toml b/pyo3cls/Cargo.toml new file mode 100644 index 00000000..9845b904 --- /dev/null +++ b/pyo3cls/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pyo3cls" +version = "0.1.0" +authors = ["Nikolay Kim "] + +[lib] +proc-macro = true + +[dependencies] +quote="0.3" + +[dependencies.syn] +version="0.11" +features=["full"] +#git = "https://github.com/dtolnay/syn.git" diff --git a/pyo3cls/src/lib.rs b/pyo3cls/src/lib.rs new file mode 100644 index 00000000..7e73cdd0 --- /dev/null +++ b/pyo3cls/src/lib.rs @@ -0,0 +1,30 @@ +#![feature(proc_macro)] + +extern crate proc_macro; +extern crate syn; +#[macro_use] extern crate quote; + +use std::str::FromStr; +use proc_macro::TokenStream; + +mod py_impl; +use py_impl::build_py_impl; + + +#[proc_macro_attribute] +pub fn py_impl(_: TokenStream, input: TokenStream) -> TokenStream { + // Construct a string representation of the type definition + let source = input.to_string(); + + // Parse the string representation into a syntax tree + //let ast: syn::Crate = source.parse().unwrap(); + let ast = syn::parse_item(&source).unwrap(); + + // Build the output + let expanded = build_py_impl(&ast); + + // Return the generated impl as a TokenStream + let s = source + expanded.as_str(); + + TokenStream::from_str(s.as_str()).unwrap() +} diff --git a/pyo3cls/src/py_impl.rs b/pyo3cls/src/py_impl.rs new file mode 100644 index 00000000..301ac816 --- /dev/null +++ b/pyo3cls/src/py_impl.rs @@ -0,0 +1,62 @@ +use syn; +use quote; + + +enum ImplType { + Buffer, +} + +pub fn build_py_impl(ast: &syn::Item) -> quote::Tokens { + match ast.node { + syn::ItemKind::Impl(_, _, _, ref path, ref ty, ref impl_items) => { + if let &Some(ref path) = path { + match process_path(path) { + ImplType::Buffer => { + impl_protocol("PyBufferProtocolImpl", path.clone(), ty, impl_items) + } + } + } else { + //ImplType::Impl + unimplemented!() + } + }, + _ => panic!("#[py_impl] can only be used with Impl blocks"), + } +} + +fn process_path(path: &syn::Path) -> ImplType { + if let Some(segment) = path.segments.last() { + match segment.ident.as_ref() { + "PyBufferProtocol" => ImplType::Buffer, + _ => panic!("#[py_impl] can not be used with this block"), + } + } else { + panic!("#[py_impl] can not be used with this block"); + } +} + +fn impl_protocol(name: &'static str, + path: syn::Path, ty: &Box, + impls: &Vec) -> quote::Tokens { + // get method names in impl block + let mut meth = Vec::new(); + for iimpl in impls.iter() { + meth.push(String::from(iimpl.ident.as_ref())) + } + + // set trait name + let mut path = path; + { + let mut last = path.segments.last_mut().unwrap(); + last.ident = syn::Ident::from(name); + } + + quote! { + impl #path for #ty { + fn methods() -> &'static [&'static str] { + static METHODS: &'static [&'static str] = &[#(#meth,),*]; + METHODS + } + } + } +} diff --git a/src/class/buffer.rs b/src/class/buffer.rs new file mode 100644 index 00000000..11091af5 --- /dev/null +++ b/src/class/buffer.rs @@ -0,0 +1,85 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use std; +use std::os::raw::c_int; + +use ffi; +use err::{PyErr, PyResult}; +use python::{self, Python, PythonObject}; +use conversion::ToPyObject; +use objects::{PyObject, PyType, PyModule}; +use py_class::slots::UnitCallbackConverter; +use function::handle_callback; + + +pub trait PyBufferProtocolImpl { + fn methods() -> &'static [&'static str]; +} + +impl PyBufferProtocolImpl for T { + default fn methods() -> &'static [&'static str] { + static METHODS: &'static [&'static str] = &[]; + METHODS + } +} + +pub trait PyBufferProtocol { + + fn bf_getbuffer(&self, py: Python, view: *mut ffi::Py_buffer, flags: c_int) -> PyResult<()>; + + fn bf_releasebuffer(&self, py: Python, view: *mut ffi::Py_buffer) -> PyResult<()>; + +} + +impl PyBufferProtocol for T { + + default fn bf_getbuffer(&self, _py: Python, + _view: *mut ffi::Py_buffer, _flags: c_int) -> PyResult<()> { + Ok(()) + } + default fn bf_releasebuffer(&self, _py: Python, + _view: *mut ffi::Py_buffer) -> PyResult<()> { + Ok(()) + } +} + + +impl ffi::PyBufferProcs { + + pub fn new() -> Option + where T: PyBufferProtocol + PyBufferProtocolImpl + PythonObject + { + let methods = T::methods(); + if methods.is_empty() { + return None + } + + let mut buf_procs: ffi::PyBufferProcs = ffi::PyBufferProcs_INIT; + + for name in methods { + match name { + &"bf_getbuffer" => { + buf_procs.bf_getbuffer = { + unsafe extern "C" fn wrap(slf: *mut ffi::PyObject, arg1: *mut ffi::Py_buffer, arg2: c_int) -> c_int + where T: PyBufferProtocol + PythonObject + { + const LOCATION: &'static str = concat!(stringify!(T), ".buffer_get::()"); + handle_callback(LOCATION, UnitCallbackConverter, + |py| { + let slf = PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::(); + let result = slf.bf_getbuffer(py, arg1, arg2); + ::PyDrop::release_ref(slf, py); + result + } + ) + } + Some(wrap::) + } + }, + _ => () + } + } + + Some(buf_procs) + } +} diff --git a/src/class/mod.rs b/src/class/mod.rs new file mode 100644 index 00000000..b49954c0 --- /dev/null +++ b/src/class/mod.rs @@ -0,0 +1,4 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +pub mod buffer; +pub use self::buffer::*; diff --git a/src/ffi/object.rs b/src/ffi/object.rs index dab4c723..7b499379 100644 --- a/src/ffi/object.rs +++ b/src/ffi/object.rs @@ -95,7 +95,7 @@ mod bufferinfo { #[derive(Copy)] pub struct Py_buffer { pub buf: *mut c_void, - pub obj: *mut ::PyObject, + pub obj: *mut ::ffi::PyObject, pub len: Py_ssize_t, pub itemsize: Py_ssize_t, pub readonly: c_int, @@ -114,12 +114,12 @@ mod bufferinfo { } pub type getbufferproc = - unsafe extern "C" fn(arg1: *mut ::PyObject, + unsafe extern "C" fn(arg1: *mut ::ffi::PyObject, arg2: *mut Py_buffer, arg3: c_int) -> c_int; pub type releasebufferproc = - unsafe extern "C" fn(arg1: *mut ::PyObject, + unsafe extern "C" fn(arg1: *mut ::ffi::PyObject, arg2: *mut Py_buffer) -> (); /// Maximum number of dimensions @@ -407,7 +407,7 @@ mod typeobject { am_anext: None, }; #[repr(C)] - #[derive(Copy)] + #[derive(Copy, Debug)] pub struct PyBufferProcs { pub bf_getbuffer: Option, pub bf_releasebuffer: Option, diff --git a/src/lib.rs b/src/lib.rs index 1f1d5433..e76ee53a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,12 @@ extern crate libc; +#[macro_use] pub extern crate pyo3cls; +pub use pyo3cls as cls; + pub mod ffi; +pub mod class; + pub use ffi::Py_ssize_t; pub use err::{PyErr, PyResult}; pub use objects::*; @@ -89,6 +94,7 @@ pub use pythonrun::{GILGuard, GILProtected, prepare_freethreaded_python}; pub use conversion::{FromPyObject, RefFromPyObject, ToPyObject}; pub use py_class::{CompareOp}; pub use objectprotocol::{ObjectProtocol}; +pub use class::*; #[allow(non_camel_case_types)] pub type Py_hash_t = ffi::Py_hash_t; diff --git a/src/objects/object.rs b/src/objects/object.rs index fa87f242..0ecc9f23 100644 --- a/src/objects/object.rs +++ b/src/objects/object.rs @@ -79,7 +79,7 @@ unsafe fn make_shared(ptr: *mut ffi::PyObject) -> *mut ffi::PyObject { #[inline] #[cfg(feature="nightly")] fn unpack_shared(ptr: ptr::Shared) -> *mut ffi::PyObject { - *ptr + ptr.as_ptr() } #[inline] diff --git a/src/py_class/slots.rs b/src/py_class/slots.rs index 5ee328fc..a17faa26 100644 --- a/src/py_class/slots.rs +++ b/src/py_class/slots.rs @@ -26,6 +26,7 @@ use objects::PyObject; use function::CallbackConverter; use err::{PyErr, PyResult}; use py_class::{CompareOp}; +use class::PyBufferProtocol; use exc; use Py_hash_t; @@ -49,7 +50,7 @@ macro_rules! py_class_type_object_static_init { tp_flags: py_class_type_object_flags!($gc), tp_traverse: py_class_tp_traverse!($class_name, $gc), .. - $crate::_detail::ffi::PyTypeObject_INIT + $crate::_detail::ffi::PyTypeObject_INIT } ); } @@ -74,6 +75,8 @@ macro_rules! py_class_type_object_flags { pub const TPFLAGS_DEFAULT : ::libc::c_ulong = ffi::Py_TPFLAGS_DEFAULT; +use class::buffer::*; + #[macro_export] #[doc(hidden)] macro_rules! py_class_type_object_dynamic_init { @@ -94,15 +97,25 @@ macro_rules! py_class_type_object_dynamic_init { $type_object.tp_basicsize = <$class as $crate::py_class::BaseObject>::size() as $crate::_detail::ffi::Py_ssize_t; } + // call slot macros outside of unsafe block *(unsafe { &mut $type_object.tp_as_async }) = py_class_as_async!($as_async); *(unsafe { &mut $type_object.tp_as_sequence }) = py_class_as_sequence!($as_sequence); *(unsafe { &mut $type_object.tp_as_number }) = py_class_as_number!($as_number); - *(unsafe { &mut $type_object.tp_as_buffer }) = py_class_as_buffer!($as_buffer); + + if let Some(buf) = $crate::ffi::PyBufferProcs::new::<$class>() { + static mut BUFFER_PROCS: $crate::ffi::PyBufferProcs = $crate::ffi::PyBufferProcs_INIT; + *(unsafe { &mut BUFFER_PROCS }) = buf; + *(unsafe { &mut $type_object.tp_as_buffer }) = unsafe { &mut BUFFER_PROCS }; + } else { + *(unsafe { &mut $type_object.tp_as_buffer }) = 0 as *mut $crate::ffi::PyBufferProcs; + } + py_class_as_mapping!($type_object, $as_mapping, $setdelitem); } } + pub fn build_tp_name(module_name: Option<&str>, type_name: &str) -> *mut c_char { let name = match module_name { Some(module_name) => CString::new(format!("{}.{}", module_name, type_name)), @@ -196,22 +209,6 @@ macro_rules! py_class_as_async { } -#[macro_export] -#[doc(hidden)] -macro_rules! py_class_as_buffer { - ([]) => (0 as *mut $crate::_detail::ffi::PyBufferProcs); - ([$( $slot_name:ident : $slot_value:expr ,)+]) => {{ - static mut BUFFER_PROCS : $crate::_detail::ffi::PyBufferProcs - = $crate::_detail::ffi::PyBufferProcs { - $( $slot_name : $slot_value, )* - .. - $crate::_detail::ffi::PyBufferProcs_INIT - }; - unsafe { &mut BUFFER_PROCS } - }} -} - - #[macro_export] #[doc(hidden)] macro_rules! py_class_as_mapping { diff --git a/tests/test_buffer_protocol.rs b/tests/test_buffer_protocol.rs new file mode 100644 index 00000000..684d919e --- /dev/null +++ b/tests/test_buffer_protocol.rs @@ -0,0 +1,78 @@ +#![allow(dead_code, unused_variables)] +#![feature(proc_macro, specialization)] + +#[macro_use] extern crate pyo3; + +use std::ptr; +use std::os::raw::{c_int, c_void}; + +use pyo3::*; +use pyo3::cls; + + +py_class!(class TestClass |py| { + data vec: Vec; +}); + +#[cls::py_impl] +impl class::PyBufferProtocol for TestClass { + fn bf_getbuffer(&self, py: Python, view: *mut ffi::Py_buffer, flags: c_int) -> PyResult<()> { + + if view == ptr::null_mut() { + return Err(PyErr::new::(py, "View is null")) + } + + unsafe { + (*view).obj = ptr::null_mut(); + } + + if (flags & ffi::PyBUF_WRITABLE) == ffi::PyBUF_WRITABLE { + return Err(PyErr::new::(py, "Object is not writable")) + } + + let bytes = self.vec(py); + + unsafe { + (*view).buf = bytes.as_ptr() as *mut c_void; + (*view).len = bytes.len() as isize; + (*view).readonly = 1; + (*view).itemsize = 1; + + (*view).format = ptr::null_mut(); + if (flags & ffi::PyBUF_FORMAT) == ffi::PyBUF_FORMAT { + let msg = ::std::ffi::CStr::from_ptr("B\0".as_ptr() as *const _); + (*view).format = msg.as_ptr() as *mut _; + } + + (*view).ndim = 1; + (*view).shape = ptr::null_mut(); + if (flags & ffi::PyBUF_ND) == ffi::PyBUF_ND { + (*view).shape = (&((*view).len)) as *const _ as *mut _; + } + + (*view).strides = ptr::null_mut(); + if (flags & ffi::PyBUF_STRIDES) == ffi::PyBUF_STRIDES { + (*view).strides = &((*view).itemsize) as *const _ as *mut _; + } + + (*view).suboffsets = ptr::null_mut(); + (*view).internal = ptr::null_mut(); + } + + Ok(()) + } +} + + +#[test] +fn test_buffer() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let t = TestClass::create_instance(py, vec![b' ', b'2', b'3']).unwrap(); + + let d = PyDict::new(py); + let _ = d.set_item(py, "ob", t); + + py.run("assert bytes(ob) == b' 23'", None, Some(&d)).unwrap(); +}