add PyAsyncProtocol

This commit is contained in:
Nikolay Kim 2017-05-14 18:55:04 -07:00
parent 088d44f8d1
commit f4feade487
10 changed files with 238 additions and 20 deletions

View File

@ -30,7 +30,7 @@ build: src/py_class/py_class_impl.rs
cargo build $(CARGO_FLAGS)
test: build
cargo test $(CARGO_FLAGS)
cargo test $(CARGO_FLAGS) --lib
#ifeq ($(NIGHTLY),1)
# ast-json output is only supported on nightly

View File

@ -7,6 +7,8 @@ extern crate syn;
use std::str::FromStr;
use proc_macro::TokenStream;
use quote::{Tokens, ToTokens};
mod py_impl;
use py_impl::build_py_impl;
@ -18,13 +20,15 @@ pub fn py_impl(_: TokenStream, input: TokenStream) -> TokenStream {
// Parse the string representation into a syntax tree
//let ast: syn::Crate = source.parse().unwrap();
let ast = syn::parse_item(&source).unwrap();
let mut ast = syn::parse_item(&source).unwrap();
// Build the output
let expanded = build_py_impl(&ast);
let expanded = build_py_impl(&mut ast);
// Return the generated impl as a TokenStream
let s = source + expanded.as_str();
let mut tokens = Tokens::new();
ast.to_tokens(&mut tokens);
let s = String::from(tokens.as_str()) + expanded.as_str();
TokenStream::from_str(s.as_str()).unwrap()
}

View File

@ -3,16 +3,22 @@ use quote;
enum ImplType {
Async,
Buffer,
}
pub fn build_py_impl(ast: &syn::Item) -> quote::Tokens {
pub fn build_py_impl(ast: &mut syn::Item) -> quote::Tokens {
match ast.node {
syn::ItemKind::Impl(_, _, _, ref path, ref ty, ref impl_items) => {
syn::ItemKind::Impl(_, _, _, ref path, ref ty, ref mut impl_items) => {
if let &Some(ref path) = path {
match process_path(path) {
ImplType::Async => {
impl_protocol("PyAsyncProtocolImpl",
path.clone(), ty, impl_items, true)
}
ImplType::Buffer => {
impl_protocol("PyBufferProtocolImpl", path.clone(), ty, impl_items)
impl_protocol("PyBufferProtocolImpl",
path.clone(), ty, impl_items, false)
}
}
} else {
@ -27,6 +33,7 @@ pub fn build_py_impl(ast: &syn::Item) -> quote::Tokens {
fn process_path(path: &syn::Path) -> ImplType {
if let Some(segment) = path.segments.last() {
match segment.ident.as_ref() {
"PyAsyncProtocol" => ImplType::Async,
"PyBufferProtocol" => ImplType::Buffer,
_ => panic!("#[py_impl] can not be used with this block"),
}
@ -37,11 +44,21 @@ fn process_path(path: &syn::Path) -> ImplType {
fn impl_protocol(name: &'static str,
path: syn::Path, ty: &Box<syn::Ty>,
impls: &Vec<syn::ImplItem>) -> quote::Tokens {
impls: &mut Vec<syn::ImplItem>, adjust_result: bool) -> 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()))
for iimpl in impls.iter_mut() {
match iimpl.node {
syn::ImplItemKind::Method(ref mut sig, ref mut block) => {
meth.push(String::from(iimpl.ident.as_ref()));
// adjust return type
if adjust_result {
impl_adjust_result(sig, block);
}
},
_ => (),
}
}
// set trait name
@ -60,3 +77,69 @@ fn impl_protocol(name: &'static str,
}
}
}
fn impl_adjust_result(sig: &mut syn::MethodSig, block: &mut syn::Block) {
match sig.decl.output {
syn::FunctionRetTy::Ty(ref mut ty) => match *ty {
syn::Ty::Path(_, ref mut path) => {
// check if function returns PyResult
if let Some(segment) = path.segments.last_mut() {
match segment.ident.as_ref() {
// check result type
"PyResult" => match segment.parameters {
syn::PathParameters::AngleBracketed(ref mut data) => {
if rewrite_pyobject(&mut data.types) {
let expr = {
let s = block as &quote::ToTokens;
quote! {
match #s {
Ok(res) => Ok(res.to_py_object(py)),
Err(err) => Err(err)
}
}
};
let expr = syn::parse_expr(&expr.as_str()).unwrap();
let expr = syn::Stmt::Expr(Box::new(expr));
block.stmts = vec![expr];
}
},
_ => (),
},
_ => (),
}
}
}
_ => (),
},
syn::FunctionRetTy::Default => (),
}
}
fn rewrite_pyobject(path: &mut Vec<syn::Ty>) -> bool {
if path.len() != 1 {
false
} else {
if let &mut syn::Ty::Path(_, ref mut path) = path.first_mut().unwrap() {
if let Some(segment) = path.segments.last_mut() {
if segment.ident.as_ref() == "PyObject" {
return false
} else {
segment.ident = syn::Ident::from("PyObject");
return true
}
}
}
let ty = syn::Ty::Path(
None, syn::Path{
global: false,
segments: vec![
syn::PathSegment {
ident: syn::Ident::from("PyObject"),
parameters: syn::PathParameters::AngleBracketed(
syn::AngleBracketedParameterData {
lifetimes: vec![], types: vec![], bindings: vec![] }) }]});
let _ = path.pop();
let _ = path.push(ty);
true
}
}

94
src/class/async.rs Normal file
View File

@ -0,0 +1,94 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
//! Represent Python Async Object Structures
//! Trait and support implementation for implementing awaitable objects
//!
//! more information on python async support
//! https://docs.python.org/3/c-api/typeobj.html#async-object-structures
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, PyObjectCallbackConverter};
use class::NO_METHODS;
/// Awaitable interface
pub trait PyAsyncProtocol {
fn am_await(&self, py: Python) -> PyResult<PyObject>;
fn am_aiter(&self, py: Python) -> PyResult<PyObject>;
fn am_anext(&self, py: Python) -> PyResult<PyObject>;
}
impl<P> PyAsyncProtocol for P {
default fn am_await(&self, py: Python) -> PyResult<PyObject> {
Ok(py.None())
}
default fn am_aiter(&self, py: Python) -> PyResult<PyObject> {
Ok(py.None())
}
default fn am_anext(&self, py: Python) -> PyResult<PyObject> {
Ok(py.None())
}
}
#[doc(hidden)]
pub trait PyAsyncProtocolImpl {
fn methods() -> &'static [&'static str];
}
impl<T> PyAsyncProtocolImpl for T {
default fn methods() -> &'static [&'static str] {
NO_METHODS
}
}
impl ffi::PyAsyncMethods {
/// Construct PyAsyncMethods struct for PyTypeObject.tp_as_async
pub fn new<T>() -> Option<ffi::PyAsyncMethods>
where T: PyAsyncProtocol + PyAsyncProtocolImpl + PythonObject
{
let methods = T::methods();
if methods.is_empty() {
return None
}
let mut meth: ffi::PyAsyncMethods = ffi::PyAsyncMethods_INIT;
for name in methods {
match name {
&"am_await" => {
meth.am_await = py_unary_slot!(
PyAsyncProtocol, T::am_await,
*mut ffi::PyObject, PyObjectCallbackConverter);
},
&"am_aiter" => {
meth.am_aiter = py_unary_slot!(
PyAsyncProtocol, T::am_aiter,
*mut ffi::PyObject, PyObjectCallbackConverter);
},
&"am_anext" => {
meth.am_anext = py_unary_slot!(
PyAsyncProtocol, T::am_anext,
*mut ffi::PyObject, PyObjectCallbackConverter);
},
_ => unreachable!(),
}
}
Some(meth)
}
}

View File

@ -14,16 +14,17 @@ use conversion::ToPyObject;
use objects::{PyObject, PyType, PyModule};
use py_class::slots::UnitCallbackConverter;
use function::handle_callback;
use self::NO_METHODS;
use class::NO_METHODS;
/// Buffer protocol interface
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<()>;
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<()>;
}
#[doc(hidden)]

23
src/class/macros.rs Normal file
View File

@ -0,0 +1,23 @@
#[macro_export]
#[doc(hidden)]
macro_rules! py_unary_slot {
($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr) => {{
unsafe extern "C" fn wrap<T>(slf: *mut $crate::ffi::PyObject) -> $res_type
where T: $trait + PythonObject
{
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::<T>();
let ret = slf.$f(py);
$crate::PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap::<T>)
}}
}

View File

@ -1,5 +1,7 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
#[macro_use] mod macros;
pub mod async;
pub mod buffer;

View File

@ -60,6 +60,8 @@ fn main() {
#[macro_export]
macro_rules! py_exception {
($module: ident, $name: ident, $base: ty) => {
use $crate::PythonObject;
pub struct $name($crate::PyObject);
pyobject_newtype!($name);

View File

@ -84,8 +84,6 @@ extern crate libc;
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::*;
@ -94,7 +92,6 @@ pub use pythonrun::{GILGuard, GILProtected, prepare_freethreaded_python};
pub use conversion::{FromPyObject, RefFromPyObject, ToPyObject, ToPyTuple};
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;
@ -168,6 +165,7 @@ macro_rules! py_impl_from_py_object_for_python_object {
}
}
pub mod py_class;
mod python;
mod err;
mod conversion;
@ -177,7 +175,8 @@ mod pythonrun;
pub mod argparse;
mod function;
pub mod buffer;
pub mod py_class;
pub mod class;
pub use class::*;
// re-export for simplicity
pub use std::os::raw::*;
@ -193,7 +192,7 @@ pub mod _detail {
}
pub use err::{from_owned_ptr_or_panic, result_from_owned_ptr};
pub use function::{handle_callback, py_fn_impl, AbortOnDrop,
PyObjectCallbackConverter, PythonObjectCallbackConverter};
PyObjectCallbackConverter, PythonObjectCallbackConverter};
}
/// Expands to an `extern "C"` function that allows Python to load

View File

@ -25,6 +25,7 @@ use conversion::ToPyObject;
use objects::PyObject;
use function::CallbackConverter;
use err::{PyErr, PyResult};
use class::*;
use py_class::{CompareOp};
use class::PyBufferProtocol;
use exc;
@ -99,10 +100,10 @@ macro_rules! py_class_type_object_dynamic_init {
}
// 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);
// buffer protocol
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;
@ -111,6 +112,15 @@ macro_rules! py_class_type_object_dynamic_init {
*(unsafe { &mut $type_object.tp_as_buffer }) = 0 as *mut $crate::ffi::PyBufferProcs;
}
// async methods
if let Some(buf) = $crate::ffi::PyAsyncMethods::new::<$class>() {
static mut ASYNC_METHODS: $crate::ffi::PyAsyncMethods = $crate::ffi::PyAsyncMethods_INIT;
*(unsafe { &mut ASYNC_METHODS }) = buf;
*(unsafe { &mut $type_object.tp_as_async }) = unsafe { &mut ASYNC_METHODS };
} else {
*(unsafe { &mut $type_object.tp_as_async }) = 0 as *mut $crate::ffi::PyAsyncMethods;
}
py_class_as_mapping!($type_object, $as_mapping, $setdelitem);
}
}