diff --git a/pyo3cls/src/lib.rs b/pyo3cls/src/lib.rs index 1615a75d..fc25aecc 100644 --- a/pyo3cls/src/lib.rs +++ b/pyo3cls/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +#![recursion_limit="1024"] #![feature(proc_macro)] extern crate proc_macro; @@ -9,12 +12,13 @@ use proc_macro::TokenStream; use quote::{Tokens, ToTokens}; -mod py_impl; -use py_impl::build_py_impl; +mod py_class; +mod py_proto; +mod py_method; #[proc_macro_attribute] -pub fn py_impl(_: TokenStream, input: TokenStream) -> TokenStream { +pub fn proto(_: TokenStream, input: TokenStream) -> TokenStream { // Construct a string representation of the type definition let source = input.to_string(); @@ -23,7 +27,28 @@ pub fn py_impl(_: TokenStream, input: TokenStream) -> TokenStream { let mut ast = syn::parse_item(&source).unwrap(); // Build the output - let expanded = build_py_impl(&mut ast); + let expanded = py_proto::build_py_proto(&mut ast); + + // Return the generated impl as a TokenStream + let mut tokens = Tokens::new(); + ast.to_tokens(&mut tokens); + let s = String::from(tokens.as_str()) + expanded.as_str(); + + TokenStream::from_str(s.as_str()).unwrap() +} + + +#[proc_macro_attribute] +pub fn class(_: 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 mut ast = syn::parse_derive_input(&source).unwrap(); + + // Build the output + let expanded = py_class::build_py_class(&mut ast); // Return the generated impl as a TokenStream let mut tokens = Tokens::new(); diff --git a/pyo3cls/src/py_class.rs b/pyo3cls/src/py_class.rs new file mode 100644 index 00000000..a0b41632 --- /dev/null +++ b/pyo3cls/src/py_class.rs @@ -0,0 +1,142 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use syn; +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 mut tokens = Tokens::new(); + 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! { + #[feature(specialization)] + #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] + const #dummy_const: () = { + extern crate pyo3; + use std; + use pyo3::ffi; + + #tokens + }; + } +} + +fn impl_to_py_object(cls: &syn::Ident) -> Tokens { + quote! { + /// Identity conversion: allows using existing `PyObject` instances where + /// `T: ToPyObject` is expected. + impl pyo3::ToPyObject for #cls where #cls: pyo3::PythonObject { + #[inline] + fn to_py_object(&self, py: pyo3::Python) -> pyo3::PyObject { + pyo3::PyClone::clone_ref(self, py).into_object() + } + + #[inline] + fn into_py_object(self, _py: pyo3::Python) -> pyo3::PyObject { + self.into_object() + } + + #[inline] + fn with_borrowed_ptr(&self, _py: pyo3::Python, f: F) -> R + where F: FnOnce(*mut pyo3::ffi::PyObject) -> R + { + f(pyo3::PythonObject::as_object(self).as_ptr()) + } + } + } +} + +fn impl_from_py_object(cls: &syn::Ident) -> Tokens { + quote! { + impl <'source> pyo3::FromPyObject<'source> for #cls { + #[inline] + fn extract(py: pyo3::Python, obj: &'source pyo3::PyObject) + -> pyo3::PyResult<#cls> { + Ok(obj.clone_ref(py).cast_into::<#cls>(py)?) + } + } + + impl <'source> pyo3::FromPyObject<'source> for &'source #cls { + #[inline] + fn extract(py: pyo3::Python, obj: &'source pyo3::PyObject) + -> pyo3::PyResult<&'source #cls> { + Ok(obj.cast_as::<#cls>(py)?) + } + } + } +} + +fn impl_python_object(cls: &syn::Ident) -> Tokens { + quote! { + impl pyo3::PythonObject for #cls { + #[inline] + fn as_object(&self) -> &pyo3::PyObject { + &self._unsafe_inner + } + + #[inline] + fn into_object(self) -> pyo3::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: pyo3::PyObject) -> Self { + #cls { _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 pyo3::PyObject) -> &'a Self { + std::mem::transmute(obj) + } + } + } +} + +fn impl_checked_downcast(cls: &syn::Ident) -> Tokens { + quote! { + impl pyo3::PythonObjectWithCheckedDowncast for #cls { + #[inline] + fn downcast_from<'p>(py: pyo3::Python<'p>, obj: pyo3::PyObject) + -> Result<#cls, pyo3::PythonObjectDowncastError<'p>> { + if py.get_type::<#cls>().is_instance(py, &obj) { + Ok(#cls { _unsafe_inner: obj }) + } else { + Err(pyo3::PythonObjectDowncastError(py)) + } + } + + #[inline] + fn downcast_borrow_from<'a, 'p>(py: pyo3::Python<'p>, obj: &'a pyo3::PyObject) + -> Result<&'a #cls, pyo3::PythonObjectDowncastError<'p>> { + if py.get_type::<#cls>().is_instance(py, obj) { + unsafe { Ok(std::mem::transmute(obj)) } + } else { + Err(pyo3::PythonObjectDowncastError(py)) + } + } + } + } +} + +fn impl_class_init(cls: &syn::Ident) -> Tokens { + quote! { + impl pyo3::class::methods::PyClassInit for #cls { + fn init() -> bool { + true + } + } + } +} diff --git a/pyo3cls/src/py_impl.rs b/pyo3cls/src/py_impl.rs deleted file mode 100644 index 12032ffb..00000000 --- a/pyo3cls/src/py_impl.rs +++ /dev/null @@ -1,145 +0,0 @@ -use syn; -use quote; - - -enum ImplType { - Async, - Buffer, -} - -pub fn build_py_impl(ast: &mut syn::Item) -> quote::Tokens { - match ast.node { - 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, false) - } - } - } 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() { - "PyAsyncProtocol" => ImplType::Async, - "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: &mut Vec, adjust_result: bool) -> quote::Tokens { - // get method names in impl block - let mut meth = Vec::new(); - 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 - 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 - } - } - } -} - -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 "e::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) -> 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 - } -} diff --git a/pyo3cls/src/py_method.rs b/pyo3cls/src/py_method.rs new file mode 100644 index 00000000..083c63ae --- /dev/null +++ b/pyo3cls/src/py_method.rs @@ -0,0 +1,234 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use syn; +use quote::Tokens; + +#[derive(Debug)] +struct Arg<'a> { + pub name: &'a syn::Ident, + pub mode: &'a syn::BindingMode, + pub ty: &'a syn::Ty, + pub optional: Option<&'a syn::Ty>, +} + + +pub fn gen_py_method<'a>(cls: &Box, name: &syn::Ident, + sig: &mut syn::MethodSig, _block: &mut syn::Block) -> Tokens { + check_generic(name, sig); + + //let mut has_self = false; + let mut py = false; + let mut arguments: Vec = Vec::new(); + + for input in sig.decl.inputs.iter() { + match input { + &syn::FnArg::SelfRef(_, _) => { + //has_self = true; + }, + &syn::FnArg::SelfValue(_) => { + //has_self = true; + } + &syn::FnArg::Captured(ref pat, ref ty) => { + let (mode, ident) = match pat { + &syn::Pat::Ident(ref mode, ref ident, _) => + (mode, ident), + _ => + panic!("unsupported argument: {:?}", pat), + }; + // TODO add check for first py: Python arg + if py { + let opt = check_arg_ty_and_optional(name, ty); + arguments.push(Arg{name: ident, mode: mode, ty: ty, optional: opt}); + } else { + py = true; + } + } + &syn::FnArg::Ignored(_) => + panic!("ignored argument: {:?}", name), + } + } + + impl_py_method_def(name, &impl_wrap(cls, name, arguments)) +} + + fn check_generic(name: &syn::Ident, sig: &syn::MethodSig) { + if !sig.generics.ty_params.is_empty() { + panic!("python method can not be generic: {:?}", name); + } +} + +fn check_arg_ty_and_optional<'a>(name: &'a syn::Ident, ty: &'a syn::Ty) -> Option<&'a syn::Ty> { + match ty { + &syn::Ty::Path(ref qs, ref path) => { + if let &Some(ref qs) = qs { + panic!("explicit Self type in a 'qualified path' is not supported: {:?} - {:?}", + name, qs); + } + + if let Some(segment) = path.segments.last() { + match segment.ident.as_ref() { + "Option" => { + match segment.parameters { + syn::PathParameters::AngleBracketed(ref params) => { + if params.types.len() != 1 { + panic!("argument type is not supported by python method: {:?} ({:?})", + name, ty); + } + Some(¶ms.types[0]) + }, + _ => { + panic!("argument type is not supported by python method: {:?} ({:?})", + name, ty); + } + } + }, + _ => None, + } + } else { + None + } + }, + _ => { + panic!("argument type is not supported by python method: {:?} ({:?})", + name, ty); + }, + } +} + +/// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords) +fn impl_wrap(cls: &Box, name: &syn::Ident, args: Vec) -> Tokens { + let cb = impl_call(cls, name, &args); + let body = impl_arg_params(args, cb); + + quote! { + unsafe extern "C" fn wrap + (slf: *mut pyo3::ffi::PyObject, + args: *mut pyo3::ffi::PyObject, + kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject + { + const LOCATION: &'static str = concat!( + stringify!(#cls), ".", stringify!(#name), "()"); + pyo3::_detail::handle_callback( + LOCATION, pyo3::_detail::PyObjectCallbackConverter, |py| + { + let args: pyo3::PyTuple = + pyo3::PyObject::from_borrowed_ptr(py, args).unchecked_cast_into(); + let kwargs: Option = pyo3::argparse::get_kwargs(py, kwargs); + + let ret = { + #body + }; + pyo3::PyDrop::release_ref(args, py); + pyo3::PyDrop::release_ref(kwargs, py); + ret + }) + } + } +} + +fn impl_call(cls: &Box, fname: &syn::Ident, args: &Vec) -> Tokens { + let names: Vec<&syn::Ident> = args.iter().map(|item| item.name).collect(); + quote! { + { + let slf = pyo3::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<#cls>(); + let ret = slf.#fname(py, #(#names),*); + pyo3::PyDrop::release_ref(slf, py); + ret + } + } +} + +fn impl_arg_params(mut args: Vec, body: Tokens) -> Tokens { + let mut params = Vec::new(); + + for arg in args.iter() { + let name = arg.name.as_ref(); + let opt = if let Some(_) = arg.optional { + syn::Ident::from("true") + } else { + syn::Ident::from("false") + }; + params.push( + quote! { + pyo3::argparse::ParamDescription{name: #name, is_optional: #opt,} + } + ); + } + let placeholders: Vec = params.iter().map( + |_| syn::Ident::from("None")).collect(); + + // generate extrat args + args.reverse(); + let mut body = body; + for arg in args.iter() { + body = impl_arg_param(&arg, &body); + } + + // create array of arguments, and then parse + quote! { + const PARAMS: &'static [pyo3::argparse::ParamDescription<'static>] = &[ + #(#params),* + ]; + + let mut output = [#(#placeholders),*]; + match pyo3::argparse::parse_args( + py, Some(LOCATION), PARAMS, &args, kwargs.as_ref(), &mut output) { + Ok(_) => { + let mut _iter = output.iter(); + + #body + }, + Err(err) => Err(err) + } + } +} + +fn impl_arg_param(arg: &Arg, body: &Tokens) -> Tokens { + let ty = arg.ty; + let name = arg.name; + + // First unwrap() asserts the iterated sequence is long enough (which should be guaranteed); + // second unwrap() asserts the parameter was not missing (which fn + // parse_args already checked for). + + if let Some(ref opt_ty) = arg.optional { + quote! { + match match _iter.next().unwrap().as_ref() { + Some(obj) => { + match <#opt_ty as pyo3::FromPyObject>::extract(py, obj) { + Ok(obj) => Ok(Some(obj)), + Err(e) => Err(e), + } + }, + None => Ok(None) + } { + Ok(#name) => #body, + Err(e) => Err(e) + } + } + } else { + quote! { + match <#ty as pyo3::FromPyObject>::extract( + py, _iter.next().unwrap().as_ref().unwrap()) + { + Ok(#name) => { + #body + } + Err(e) => Err(e) + } + } + } +} + +fn impl_py_method_def(name: &syn::Ident, wrapper: &Tokens) -> Tokens { + quote! {{ + #wrapper + + pyo3::class::PyMethodDef { + ml_name: stringify!(#name), + ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(wrap), + ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS, + ml_doc: "", + } + }} +} diff --git a/pyo3cls/src/py_proto.rs b/pyo3cls/src/py_proto.rs new file mode 100644 index 00000000..f839a581 --- /dev/null +++ b/pyo3cls/src/py_proto.rs @@ -0,0 +1,223 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use syn; +use quote::{Tokens, ToTokens}; + +use py_method; + + +struct Methods { + methods: &'static [&'static str], + non_pyobj_result: &'static [&'static str], +} + +static DEFAULT_METHODS: Methods = Methods { + methods: &[], + non_pyobj_result: &[], +}; + +static BUFFER_METHODS: Methods = Methods { + methods: &[], + non_pyobj_result: &["bf_getbuffer", "bf_releasebuffer"], +}; + +static GC_METHODS: Methods = Methods { + methods: &[], + non_pyobj_result: &["__traverse__", "__clear__"], +}; + +static CONTEXT_METHODS: Methods = Methods { + methods: &["__enter__", "__exit__"], + non_pyobj_result: &[], +}; + + +enum ImplType { + Async, + Buffer, + Context, + GC, +} + +pub fn build_py_proto(ast: &mut syn::Item) -> Tokens { + match ast.node { + 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("pyo3::class::async::PyAsyncProtocolImpl", + path.clone(), ty, impl_items, &DEFAULT_METHODS) + } + ImplType::Buffer => { + impl_protocol("pyo3::class::buffer::PyBufferProtocolImpl", + path.clone(), ty, impl_items, &BUFFER_METHODS) + } + ImplType::Context => { + impl_protocol("pyo3::class::context::PyContextProtocolImpl", + path.clone(), ty, impl_items, &CONTEXT_METHODS) + } + ImplType::GC => { + impl_protocol("pyo3::class::gc::PyGCProtocolImpl", + path.clone(), ty, impl_items, &GC_METHODS) + } + } + } else { + panic!("#[py_proto] can only be used with protocol trait implementations") + } + }, + _ => panic!("#[py_proto] 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() { + "PyAsyncProtocol" => ImplType::Async, + "PyBufferProtocol" => ImplType::Buffer, + "PyContextProtocol" => ImplType::Context, + "PyGCProtocol" => ImplType::GC, + _ => panic!("#[py_proto] can not be used with this block"), + } + } else { + panic!("#[py_proto] can not be used with this block"); + } +} + +fn impl_protocol(name: &'static str, + path: syn::Path, ty: &Box, + impls: &mut Vec, methods: &Methods) -> Tokens { + let mut py_methods = Vec::new(); + + // get method names in impl block + let mut meth = Vec::new(); + for iimpl in impls.iter_mut() { + match iimpl.node { + syn::ImplItemKind::Method(ref mut sig, ref mut block) => { + if methods.methods.contains(&iimpl.ident.as_ref()) { + py_methods.push(py_method::gen_py_method(ty, &iimpl.ident, sig, block)); + } else { + meth.push(String::from(iimpl.ident.as_ref())); + + // adjust return type + if !methods.non_pyobj_result.contains(&iimpl.ident.as_ref()) { + impl_adjust_result(sig, block); + } + } + }, + _ => (), + } + } + + // set trait name + let mut path = path; + { + let mut last = path.segments.last_mut().unwrap(); + last.ident = syn::Ident::from(name); + } + + let i = syn::Ident::from(name); + let tokens = if py_methods.is_empty() { + quote! { + impl #i for #ty { + fn methods() -> &'static [&'static str] { + static METHODS: &'static [&'static str] = &[#(#meth),*]; + METHODS + } + } + } + } else { + quote! { + impl #i for #ty { + fn methods() -> &'static [&'static str] { + static METHODS: &'static [&'static str] = &[#(#meth,),*]; + METHODS + } + + fn py_methods() -> &'static [pyo3::class::PyMethodDef] { + static METHODS: &'static [pyo3::class::PyMethodDef] = &[ + #(#py_methods),* + ]; + METHODS + } + } + } + }; + let name = name.split("::").last().unwrap(); + let dummy_const = syn::Ident::new(format!("_IMPL_PYO3_{}", name)); + quote! { + #[feature(specialization)] + #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] + const #dummy_const: () = { + extern crate pyo3; + use pyo3::ffi; + + #tokens + }; + } +} + +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 &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) -> 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 + } +} diff --git a/src/argparse.rs b/src/argparse.rs index b19864c4..d239faa2 100644 --- a/src/argparse.rs +++ b/src/argparse.rs @@ -42,14 +42,13 @@ pub struct ParamDescription<'a> { /// * kwargs: Keyword arguments /// * output: Output array that receives the arguments. /// Must have same length as `params` and must be initialized to `None`. -pub fn parse_args( - py: Python, - fname: Option<&str>, params: &[ParamDescription], - args: &PyTuple, kwargs: Option<&PyDict>, - output: &mut[Option] -) -> PyResult<()> +pub fn parse_args(py: Python, + fname: Option<&str>, params: &[ParamDescription], + args: &PyTuple, kwargs: Option<&PyDict>, + output: &mut[Option]) -> PyResult<()> { assert!(params.len() == output.len()); + let nargs = args.len(py); let nkeywords = kwargs.map_or(0, |d| d.len(py)); if nargs + nkeywords > params.len() { diff --git a/src/class/async.rs b/src/class/async.rs index 854b2abf..1d6c3ebe 100644 --- a/src/class/async.rs +++ b/src/class/async.rs @@ -19,26 +19,38 @@ use class::NO_METHODS; /// Awaitable interface pub trait PyAsyncProtocol { - fn am_await(&self, py: Python) -> PyResult; + fn __await__(&self, py: Python) -> PyResult; - fn am_aiter(&self, py: Python) -> PyResult; + fn __aiter__(&self, py: Python) -> PyResult; - fn am_anext(&self, py: Python) -> PyResult; + fn __anext__(&self, py: Python) -> PyResult; + + fn __aenter__(&self, py: Python) -> PyResult; + + fn __aexit__(&self, py: Python) -> PyResult; } impl

PyAsyncProtocol for P { - default fn am_await(&self, py: Python) -> PyResult { + default fn __await__(&self, py: Python) -> PyResult { Ok(py.None()) } - default fn am_aiter(&self, py: Python) -> PyResult { + default fn __aiter__(&self, py: Python) -> PyResult { Ok(py.None()) } - default fn am_anext(&self, py: Python) -> PyResult { + default fn __anext__(&self, py: Python) -> PyResult { + Ok(py.None()) + } + + default fn __aenter__(&self, py: Python) -> PyResult { + Ok(py.None()) + } + + default fn __aexit__(&self, py: Python) -> PyResult { Ok(py.None()) } } @@ -70,19 +82,19 @@ impl ffi::PyAsyncMethods { for name in methods { match name { - &"am_await" => { + &"__await__" => { meth.am_await = py_unary_slot!( - PyAsyncProtocol, T::am_await, + PyAsyncProtocol, T::__await__, *mut ffi::PyObject, PyObjectCallbackConverter); }, - &"am_aiter" => { + &"__aiter__" => { meth.am_aiter = py_unary_slot!( - PyAsyncProtocol, T::am_aiter, + PyAsyncProtocol, T::__aiter__, *mut ffi::PyObject, PyObjectCallbackConverter); }, - &"am_anext" => { + &"__anext__" => { meth.am_anext = py_unary_slot!( - PyAsyncProtocol, T::am_anext, + PyAsyncProtocol, T::__anext__, *mut ffi::PyObject, PyObjectCallbackConverter); }, _ => unreachable!(), diff --git a/src/class/basic.rs b/src/class/basic.rs new file mode 100644 index 00000000..f833b171 --- /dev/null +++ b/src/class/basic.rs @@ -0,0 +1,35 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +//! Basic Python Object customization +//! +//! more information on python async support +//! https://docs.python.org/3/reference/datamodel.html#basic-customization + +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; + + +/// Basic customization +pub trait PyObjectProtocol { + + // fn __new__(&self, py: Python) -> PyResult; + + fn __str__(&self, py: Python) -> PyResult; + + fn __repr__(&self, py: Python) -> PyResult; + + fn __hash__(&self, py: Python) -> PyResult; + + fn __bool__(&self, py: Python) -> PyResult; + + fn __richcmp__(&self, other: PyObject, op: pyo3::CompareOp) -> PyResult; + + fn __call__(&self) -> PyResult; + +} diff --git a/src/class/context.rs b/src/class/context.rs new file mode 100644 index 00000000..208a0b38 --- /dev/null +++ b/src/class/context.rs @@ -0,0 +1,96 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +//! Context manager api +//! Trait and support implementation for context manager api +//! + +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, NO_PY_METHODS}; + + +/// Awaitable interface +pub trait PyContextProtocol { + + fn __enter__(&self, py: Python) -> PyResult; + + fn __exit__(&self, py: Python, + exc_type: Option, + exc_value: Option, + traceback: Option) -> PyResult; +} + + +impl

PyContextProtocol for P { + + default fn __enter__(&self, py: Python) -> PyResult { + Ok(py.None()) + } + + default fn __exit__(&self, py: Python, + _exc_type: Option, + _exc_value: Option, + _traceback: Option) -> PyResult { + Ok(py.None()) + } +} + + +#[doc(hidden)] +pub trait PyContextProtocolImpl { + fn methods() -> &'static [&'static str]; + + fn py_methods() -> &'static [::class::PyMethodDef]; +} + +impl PyContextProtocolImpl for T { + default fn methods() -> &'static [&'static str] { + NO_METHODS + } + + default fn py_methods() -> &'static [::class::PyMethodDef] { + NO_PY_METHODS + } +} + +/* + /// Construct PyAsyncMethods struct for PyTypeObject.tp_as_async + pub fn new() -> Option + 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) + } +*/ diff --git a/src/class/gc.rs b/src/class/gc.rs new file mode 100644 index 00000000..99fc6c31 --- /dev/null +++ b/src/class/gc.rs @@ -0,0 +1,118 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +//! Python GC support +//! + +use std::mem; +use std::os::raw::{c_int, c_void}; + +use ffi; +use err::{PyErr, PyResult}; +use python::{self, Python, PythonObject, PyDrop, ToPythonPointer}; +use conversion::ToPyObject; +use objects::{PyObject, PyType, PyModule}; +use py_class::slots::UnitCallbackConverter; +use function::{handle_callback, PyObjectCallbackConverter, AbortOnDrop}; +use class::NO_METHODS; + +pub struct PyTraverseError(c_int); + +/// GC support +pub trait PyGCProtocol { + + fn __traverse__(&self, py: Python, visit: PyVisit) -> Result<(), PyTraverseError>; + + fn __clear__(&self, py: Python); + +} + +impl PyGCProtocol for T { + default fn __traverse__(&self, _: Python, _: PyVisit) -> Result<(), PyTraverseError> { + Ok(()) + } + + default fn __clear__(&self, _: Python) {} +} + +#[doc(hidden)] +pub trait PyGCProtocolImpl { + fn methods() -> &'static [&'static str]; + + fn update_type_object(type_object: &mut ffi::PyTypeObject); +} + +impl PyGCProtocolImpl for T where T: PyGCProtocol + PythonObject { + default fn methods() -> &'static [&'static str] { + NO_METHODS + } + + fn update_type_object(type_object: &mut ffi::PyTypeObject) { + if ::methods().is_empty() { + type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT + } else { + type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC; + type_object.tp_traverse = Some(tp_traverse::); + type_object.tp_clear = Some(tp_clear::); + } + } +} + + +#[derive(Copy, Clone)] +pub struct PyVisit<'a> { + visit: ffi::visitproc, + arg: *mut c_void, + /// VisitProc contains a Python instance to ensure that + /// 1) it is cannot be moved out of the traverse() call + /// 2) it cannot be sent to other threads + _py: Python<'a> +} + +impl <'a> PyVisit<'a> { + pub fn call(&self, obj: &T) -> Result<(), PyTraverseError> + where T: PythonObject + { + let r = unsafe { (self.visit)(obj.as_ptr(), self.arg) }; + if r == 0 { + Ok(()) + } else { + Err(PyTraverseError(r)) + } + } +} + +#[doc(hidden)] +unsafe extern "C" fn tp_traverse(slf: *mut ffi::PyObject, + visit: ffi::visitproc, + arg: *mut c_void) -> c_int + where T: PyGCProtocol + PythonObject +{ + const LOCATION: &'static str = concat!(stringify!(T), ".__traverse__()"); + + let guard = AbortOnDrop(LOCATION); + let py = Python::assume_gil_acquired(); + let visit = PyVisit { visit: visit, arg: arg, _py: py }; + let slf = PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::(); + + let ret = match T::__traverse__(&slf, py, visit) { + Ok(()) => 0, + Err(PyTraverseError(code)) => code + }; + slf.release_ref(py); + mem::forget(guard); + ret +} + +unsafe extern "C" fn tp_clear(slf: *mut ffi::PyObject) -> c_int + where T: PyGCProtocol + PythonObject +{ + const LOCATION: &'static str = concat!(stringify!(T), ".__clear__()"); + + let guard = AbortOnDrop(LOCATION); + let py = Python::assume_gil_acquired(); + let slf = PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::(); + T::__clear__(&slf, py); + slf.release_ref(py); + mem::forget(guard); + 0 +} diff --git a/src/class/methods.rs b/src/class/methods.rs new file mode 100644 index 00000000..78ae1687 --- /dev/null +++ b/src/class/methods.rs @@ -0,0 +1,147 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use std::mem; +use std::ptr; +use std::ffi::CString; + +use ::{ffi, exc, class, py_class, PyErr, Python, PyResult, PythonObject}; +use objects::PyType; +use function::AbortOnDrop; + + +#[derive(Copy, Clone)] +pub enum PyMethodType { + PyCFunction(ffi::PyCFunction), + PyCFunctionWithKeywords(ffi::PyCFunctionWithKeywords), +} + +#[derive(Copy, Clone)] +pub struct PyMethodDef { + pub ml_name: &'static str, + pub ml_meth: PyMethodType, + pub ml_flags: ::c_int, + pub ml_doc: &'static str, +} + +unsafe impl Sync for PyMethodDef {} +unsafe impl Sync for ffi::PyMethodDef {} + + +pub trait PyClassInit { + + fn init() -> bool; + + fn type_object() -> &'static mut ffi::PyTypeObject; + + fn build_type(py: Python, + module_name: Option<&str>, + type_object: &mut ffi::PyTypeObject) -> PyResult; + +} + +impl PyClassInit for T where T: PythonObject + py_class::BaseObject { + + default fn init() -> bool { false } + + default fn type_object() -> &'static mut ffi::PyTypeObject { + static mut TYPE_OBJECT: ffi::PyTypeObject = ffi::PyTypeObject_INIT; + unsafe { + &mut TYPE_OBJECT + } + } + + default fn build_type(py: Python, module_name: Option<&str>, + type_object: &mut ffi::PyTypeObject) -> PyResult { + // type name + let name = match module_name { + Some(module_name) => CString::new( + format!("{}.{}", module_name, stringify!(type_name))), + None => CString::new(stringify!(type_name)) + }; + let name = name.expect( + "Module name/type name must not contain NUL byte").into_raw(); + + type_object.tp_name = name; + + // dealloc + type_object.tp_dealloc = Some(tp_dealloc_callback::); + + // GC support + //::update_type_object(type_object); + + // type size + type_object.tp_basicsize = ::size() as ffi::Py_ssize_t; + + // buffer protocol + if let Some(buf) = ffi::PyBufferProcs::new::() { + static mut BUFFER_PROCS: ffi::PyBufferProcs = ffi::PyBufferProcs_INIT; + *(unsafe { &mut BUFFER_PROCS }) = buf; + type_object.tp_as_buffer = unsafe { &mut BUFFER_PROCS }; + } else { + type_object.tp_as_buffer = 0 as *mut ffi::PyBufferProcs; + } + + // async methods + if let Some(buf) = ffi::PyAsyncMethods::new::() { + static mut ASYNC_METHODS: ffi::PyAsyncMethods = ffi::PyAsyncMethods_INIT; + *(unsafe { &mut ASYNC_METHODS }) = buf; + type_object.tp_as_async = unsafe { &mut ASYNC_METHODS }; + } else { + type_object.tp_as_async = 0 as *mut ffi::PyAsyncMethods; + } + + // normal methods + let mut methods = class::methods::py_class_method_defs::(); + 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(); + } + + unsafe { + if ffi::PyType_Ready(type_object) == 0 { + Ok(PyType::from_type_ptr(py, type_object)) + } else { + Err(PyErr::fetch(py)) + } + } + } +} + +pub unsafe extern "C" fn tp_dealloc_callback(obj: *mut ffi::PyObject) + where T: py_class::BaseObject +{ + let guard = AbortOnDrop("Cannot unwind out of tp_dealloc"); + let py = Python::assume_gil_acquired(); + let r = T::dealloc(py, obj); + mem::forget(guard); + r +} + +pub fn py_class_method_defs() -> Vec { + let mut defs = Vec::new(); + + for def in ::py_methods() { + let meth = match def.ml_meth { + PyMethodType::PyCFunction(meth) => meth, + PyMethodType::PyCFunctionWithKeywords(meth) => + unsafe { + ::std::mem::transmute::< + ffi::PyCFunctionWithKeywords, ffi::PyCFunction>(meth) + } + }; + + let fdef = ffi::PyMethodDef { + ml_name: CString::new(def.ml_name).expect( + "Method name must not contain NULL byte").into_raw(), + ml_meth: Some(meth), + ml_flags: def.ml_flags, + ml_doc: 0 as *const ::c_char, + }; + defs.push(fdef) + } + + defs +} diff --git a/src/class/mod.rs b/src/class/mod.rs index e12dc569..9a5ddf6f 100644 --- a/src/class/mod.rs +++ b/src/class/mod.rs @@ -4,8 +4,17 @@ pub mod async; pub mod buffer; +pub mod context; +pub mod methods; +pub mod gc; pub use self::async::*; pub use self::buffer::*; +pub use self::context::*; +pub use self::gc::{PyVisit, PyGCProtocol, PyTraverseError}; +pub use self::methods::{PyMethodDef, PyMethodType}; + +use self::gc::PyGCProtocolImpl; pub static NO_METHODS: &'static [&'static str] = &[]; +pub static NO_PY_METHODS: &'static [PyMethodDef] = &[]; diff --git a/src/ffi/methodobject.rs b/src/ffi/methodobject.rs index 7f6167c2..2b64a260 100644 --- a/src/ffi/methodobject.rs +++ b/src/ffi/methodobject.rs @@ -25,7 +25,7 @@ pub type _PyCFunctionFast = pub type PyCFunctionWithKeywords = unsafe extern "C" fn - (slf: *mut PyObject, args: *mut PyObject, + (slf: *mut PyObject, args: *mut PyObject, kwds: *mut PyObject) -> *mut PyObject; pub type PyNoArgsFunction = unsafe extern "C" fn(slf: *mut PyObject) @@ -48,6 +48,13 @@ pub struct PyMethodDef { pub ml_doc: *const c_char, } +pub const PyMethodDef_INIT : PyMethodDef = PyMethodDef { + ml_name: 0 as *const _, + ml_meth: None, + ml_flags: 0, + ml_doc: 0 as *const _, +}; + impl Clone for PyMethodDef { #[inline] fn clone(&self) -> PyMethodDef { *self } } diff --git a/src/lib.rs b/src/lib.rs index 2368e4d1..8ef265a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,11 +16,13 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -#![cfg_attr(feature="nightly", feature( - const_fn, // for GILProtected::new (#24111) - shared, // for std::ptr::Shared (#27730) - specialization, // for impl FromPyObject<'source> for Vec<...> (#31844) -))] +//#![cfg_attr(feature="nightly", feature( +// const_fn, // for GILProtected::new (#24111) +// shared, // for std::ptr::Shared (#27730) +// specialization, // for impl FromPyObject<'source> for Vec<...> (#31844) +//))] + +#![feature(specialization, shared, const_fn)] #![allow(unused_imports)] // because some imports are only necessary with python 2.x or 3.x diff --git a/src/py_class/gc.rs b/src/py_class/gc.rs deleted file mode 100644 index ef713869..00000000 --- a/src/py_class/gc.rs +++ /dev/null @@ -1,173 +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::os::raw::{c_void, c_int}; -use ffi; -use std::mem; -use python::{Python, PythonObject, PyDrop, ToPythonPointer}; -use objects::PyObject; -use function::AbortOnDrop; - -// TODO: what's the semantics of the traverse return code? -// If it's just a normal python exception, we might want to use PyErr instead. -pub struct TraverseError(c_int); - -#[derive(Copy, Clone)] -pub struct VisitProc<'a> { - visit: ffi::visitproc, - arg: *mut c_void, - /// VisitProc contains a Python instance to ensure that - /// 1) it is cannot be moved out of the traverse() call - /// 2) it cannot be sent to other threads - _py: Python<'a> -} - -impl <'a> VisitProc<'a> { - pub fn call(&self, obj: &T) -> Result<(), TraverseError> - where T: PythonObject - { - let r = unsafe { (self.visit)(obj.as_ptr(), self.arg) }; - if r == 0 { - Ok(()) - } else { - Err(TraverseError(r)) - } - } -} - -#[macro_export] -#[doc(hidden)] -macro_rules! py_class_tp_traverse { - ($class_name:ident, - /* gc: */ { - /* traverse_proc: */ None, - /* traverse_data: */ [ ] - }) => { - // If there's nothing to traverse, we don't need to generate - // tp_traverse. - // Note that in this case, py_class_type_object_flags! must not - // use Py_TPFLAGS_HAVE_GC. - None - }; - ($class_name:ident, - /* gc: */ { - $traverse_proc: expr, - /* traverse_data: */ [] - }) => {{ - unsafe extern "C" fn tp_traverse( - slf: *mut $crate::_detail::ffi::PyObject, - visit: $crate::_detail::ffi::visitproc, - arg: *mut $crate::_detail::libc::c_void - ) -> $crate::_detail::libc::c_int - { - $crate::py_class::gc::tp_traverse::<$class_name, _>( - concat!(stringify!($class_name), ".__traverse__"), - slf, visit, arg, $traverse_proc) - } - Some(tp_traverse) - }}; -} - -#[doc(hidden)] -pub unsafe fn tp_traverse( - location: &str, - slf: *mut ffi::PyObject, - visit: ffi::visitproc, - arg: *mut c_void, - callback: F) -> c_int -where C: PythonObject, - F: FnOnce(&C, Python, VisitProc) -> Result<(), TraverseError> -{ - let guard = AbortOnDrop(location); - let py = Python::assume_gil_acquired(); - let visit = VisitProc { visit: visit, arg: arg, _py: py }; - let slf = PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::(); - let ret = match callback(&slf, py, visit) { - Ok(()) => 0, - Err(TraverseError(code)) => code - }; - slf.release_ref(py); - mem::forget(guard); - ret -} - -#[macro_export] -#[doc(hidden)] -macro_rules! py_class_tp_clear { - ($class_name:ident) => {{ - unsafe extern "C" fn tp_clear( - slf: *mut $crate::_detail::ffi::PyObject - ) -> $crate::_detail::libc::c_int - { - $crate::py_class::gc::tp_clear::<$class_name, _>( - concat!(stringify!($class_name), ".__clear__"), - slf, $class_name::__clear__) - } - Some(tp_clear) - }} -} - -#[doc(hidden)] -pub unsafe fn tp_clear( - location: &str, - slf: *mut ffi::PyObject, - callback: F) -> c_int -where C: PythonObject, - F: FnOnce(&C, Python) -{ - let guard = AbortOnDrop(location); - let py = Python::assume_gil_acquired(); - let slf = PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::(); - callback(&slf, py); - slf.release_ref(py); - mem::forget(guard); - 0 -} - -/* -/// Trait that has to be implemented by `#[gc_traverse]` members. -pub trait Traversable { - /// Call VisitProc for all python objects owned by this value. - fn traverse(&self, py: Python, visit: VisitProc) -> Result<(), TraverseError>; -} - -impl Traversable for T where T: PythonObject { - fn traverse(&self, _py: Python, visit: VisitProc) -> Result<(), TraverseError> { - visit.call(self.as_object()) - } -} - -impl Traversable for Option where T: Traversable { - fn traverse(&self, py: Python, visit: VisitProc) -> Result<(), TraverseError> { - match *self { - Some(ref val) => val.traverse(py, visit), - None => Ok(()) - } - } -} - -impl Traversable for Vec where T: Traversable { - fn traverse(&self, py: Python, visit: VisitProc) -> Result<(), TraverseError> { - for val in self { - try!(val.traverse(py, visit)); - } - Ok(()) - } -} -*/ - diff --git a/src/py_class/mod.rs b/src/py_class/mod.rs index 0b0e6b9d..c47e5fc9 100644 --- a/src/py_class/mod.rs +++ b/src/py_class/mod.rs @@ -21,7 +21,6 @@ mod py_class_impl; #[doc(hidden)] pub mod slots; #[doc(hidden)] pub mod members; #[doc(hidden)] pub mod properties; -pub mod gc; use std::os::raw::c_void; use std::{mem, ptr, cell}; diff --git a/src/py_class/py_class_impl.py b/src/py_class/py_class_impl.py index 126ecd83..be8da0cc 100644 --- a/src/py_class/py_class_impl.py +++ b/src/py_class/py_class_impl.py @@ -789,25 +789,6 @@ special_names = { # With statement context managers '__enter__': normal_method(), '__exit__': normal_method(), - - # Coroutines - '__await__': operator('am_await'), - '__aiter__': operator('am_aiter'), - '__anext__': operator('am_anext'), - '__aenter__': unimplemented(), - '__aexit__': unimplemented(), - - # Buffer - '__buffer_get__': operator( - "bf_getbuffer", - args=[Argument('view', '*mut $crate::_detail::ffi::Py_buffer'), Argument('flags', 'int')], - wrapper="py_class_ternary_internal", - res_type='bool', res_conv='$crate::py_class::slots::SuccessConverter', - res_ffi_type = '$crate::_detail::libc::c_int'), - '__buffer_release__': operator( - "bf_releasebuffer", - args=[Argument('view', '*mut $crate::_detail::ffi::Py_buffer')], - wrapper="py_class_binary_internal", res_type='void'), } def main(): diff --git a/src/py_class/py_class_impl.rs b/src/py_class/py_class_impl.rs index 7cd02732..b3012347 100644 --- a/src/py_class/py_class_impl.rs +++ b/src/py_class/py_class_impl.rs @@ -28,7 +28,6 @@ macro_rules! py_class_impl { // TT muncher macro. Results are accumulated in $info $slots $impls and $members. - // Base case: we're done munching and can start producing code: { {} $class:ident $py:ident @@ -42,58 +41,10 @@ macro_rules! py_class_impl { $slots:tt { $( $imp:item )* } $members:tt $properties:tt } => { py_coerce_item! { + #[$crate::cls::class] $($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, )* ); @@ -177,13 +128,8 @@ macro_rules! py_class_impl { 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)) - } - } + unsafe { <$class as $crate::class::methods::PyClassInit> + ::build_type($py, module_name, &mut TYPE_OBJECT) } } } } @@ -248,77 +194,6 @@ macro_rules! py_class_impl { } $members $properties }}; - { { def __traverse__(&$slf:tt, $visit:ident) $body:block $($tail:tt)* } - $class:ident $py:ident - /* info: */ { - $base_type: ty, - $size: expr, - $class_visibility: tt, - /* gc: */ { - /* traverse_proc: */ None, - $traverse_data: tt - }, - $datas: tt - } - $slots:tt - { $( $imp:item )* } - $members:tt $properties:tt - } => { py_class_impl! { - { $($tail)* } - $class $py - /* info: */ { - $base_type, - $size, - $class_visibility, - /* gc: */ { - /* traverse_proc: */ $class::__traverse__, - $traverse_data - }, - $datas - } - $slots - /* impl: */ { - $($imp)* - py_coerce_item!{ - impl $class { - fn __traverse__(&$slf, - $py: $crate::Python, - $visit: $crate::py_class::gc::VisitProc) - -> Result<(), $crate::py_class::gc::TraverseError> - $body - } - } - } - $members $properties - }}; - { { def __clear__ (&$slf:ident) $body:block $($tail:tt)* } - $class:ident $py:ident $info:tt - /* slots: */ { - /* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ] - $as_async:tt $as_number:tt $as_sequence:tt $as_mapping:tt $as_buffer:tt $setdelitem:tt - } - { $( $imp:item )* } - $members:tt $properties:tt - } => { py_class_impl! { - { $($tail)* } - $class $py $info - /* slots: */ { - /* type_slots */ [ - $( $tp_slot_name : $tp_slot_value, )* - tp_clear: py_class_tp_clear!($class), - ] - $as_async $as_number $as_sequence $as_mapping $as_buffer $setdelitem - } - /* impl: */ { - $($imp)* - py_coerce_item!{ - impl $class { - fn __clear__(&$slf, $py: $crate::Python) $body - } - } - } - $members $properties - }}; { { def __abs__(&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)* } $class:ident $py:ident $info:tt /* slots: */ { @@ -474,39 +349,6 @@ macro_rules! py_class_impl { $members $properties }}; - { { def __anext__ $($tail:tt)* } $( $stuff:tt )* } => { - py_error! { "Invalid signature for operator __anext__" } - }; - { { def __await__(&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)* } - $class:ident $py:ident $info:tt - /* slots: */ { - $type_slots:tt - /* as_async */ [ $( $am_slot_name:ident : $am_slot_value:expr, )* ] - $as_number:tt $as_sequence:tt $as_mapping:tt $as_buffer:tt $setdelitem:tt - } - { $( $imp:item )* } - $members:tt $properties:tt - } => { py_class_impl! { - { $($tail)* } - $class $py $info - /* slots: */ { - $type_slots - /* as_async */ [ - $( $am_slot_name : $am_slot_value, )* - am_await: py_class_unary_slot!($class::__await__, *mut $crate::_detail::ffi::PyObject, $crate::_detail::PyObjectCallbackConverter), - ] - $as_number $as_sequence $as_mapping $as_buffer $setdelitem - } - /* impl: */ { - $($imp)* - py_class_impl_item! { $class, $py, __await__(&$slf,) $res_type; { $($body)* } [] } - } - $members $properties - }}; - - { { def __await__ $($tail:tt)* } $( $stuff:tt )* } => { - py_error! { "Invalid signature for operator __await__" } - }; { { def __bool__(&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)* } $class:ident $py:ident $info:tt /* slots: */ { @@ -537,66 +379,6 @@ macro_rules! py_class_impl { { { def __bool__ $($tail:tt)* } $( $stuff:tt )* } => { py_error! { "Invalid signature for operator __bool__" } }; - { { def __buffer_get__(&$slf:ident, $view:ident , $flags:ident ) -> $res_type:ty { $($body:tt)* } $($tail:tt)* } - $class:ident $py:ident $info:tt - /* slots: */ { - $type_slots:tt $as_async:tt $as_number:tt $as_sequence:tt $as_mapping:tt - /* as_buffer */ [ $( $bf_slot_name:ident : $bf_slot_value:expr, )* ] - $setdelitem:tt - } - { $( $imp:item )* } - $members:tt $properties:tt - } => { py_class_impl! { - { $($tail)* } - $class $py $info - /* slots: */ { - $type_slots $as_async $as_number $as_sequence $as_mapping - /* as_buffer */ [ - $( $bf_slot_name : $bf_slot_value, )* - bf_getbuffer: py_class_ternary_internal!($class::__buffer_get__, *mut $crate::_detail::ffi::Py_buffer, $crate::_detail::libc::c_int, $crate::_detail::libc::c_int, $crate::py_class::slots::SuccessConverter), - ] - $setdelitem - } - /* impl: */ { - $($imp)* - py_class_impl_item! { $class, $py, __buffer_get__(&$slf,) $res_type; { $($body)* } [{ $view : *mut $crate::_detail::ffi::Py_buffer = {} } { $flags : $crate::_detail::libc::c_int = {} }] } - } - $members $properties - }}; - - { { def __buffer_get__ $($tail:tt)* } $( $stuff:tt )* } => { - py_error! { "Invalid signature for operator __buffer_get__" } - }; - { { def __buffer_release__(&$slf:ident, $view:ident ) -> $res_type:ty { $($body:tt)* } $($tail:tt)* } - $class:ident $py:ident $info:tt - /* slots: */ { - $type_slots:tt $as_async:tt $as_number:tt $as_sequence:tt $as_mapping:tt - /* as_buffer */ [ $( $bf_slot_name:ident : $bf_slot_value:expr, )* ] - $setdelitem:tt - } - { $( $imp:item )* } - $members:tt $properties:tt - } => { py_class_impl! { - { $($tail)* } - $class $py $info - /* slots: */ { - $type_slots $as_async $as_number $as_sequence $as_mapping - /* as_buffer */ [ - $( $bf_slot_name : $bf_slot_value, )* - bf_releasebuffer: py_class_binary_slot!($class::__buffer_release__, *mut $crate::_detail::ffi::Py_buffer, *mut $crate::_detail::ffi::PyObject, $crate::_detail::PythonObjectCallbackConverter::<$crate::void>(::std::marker::PhantomData)), - ] - $setdelitem - } - /* impl: */ { - $($imp)* - py_class_impl_item! { $class, $py, __buffer_release__(&$slf,) $res_type; { $($body)* } [{ $view : *mut $crate::_detail::ffi::Py_buffer = {} }] } - } - $members $properties - }}; - - { { def __buffer_release__ $($tail:tt)* } $( $stuff:tt )* } => { - py_error! { "Invalid signature for operator __buffer_release__" } - }; { { def __call__ (&$slf:ident) -> $res_type:ty { $( $body:tt )* } $($tail:tt)* } $class:ident $py:ident $info:tt /* slots: */ { diff --git a/src/py_class/slots.rs b/src/py_class/slots.rs index 95488ee3..dac7731c 100644 --- a/src/py_class/slots.rs +++ b/src/py_class/slots.rs @@ -47,37 +47,13 @@ macro_rules! py_class_type_object_static_init { }) => ( $crate::_detail::ffi::PyTypeObject { $( $slot_name : $slot_value, )* - tp_dealloc: Some($crate::py_class::slots::tp_dealloc_callback::<$class_name>), - tp_flags: py_class_type_object_flags!($gc), - tp_traverse: py_class_tp_traverse!($class_name, $gc), + //tp_traverse: py_class_tp_traverse!($class_name, $gc), .. $crate::_detail::ffi::PyTypeObject_INIT } ); } -#[macro_export] -#[doc(hidden)] -macro_rules! py_class_type_object_flags { - (/* gc: */ { - /* traverse_proc: */ None, - /* traverse_data: */ [ /*name*/ ] - }) => { - $crate::py_class::slots::TPFLAGS_DEFAULT - }; - (/* gc: */ { - $traverse_proc: expr, - $traverse_data: tt - }) => { - $crate::py_class::slots::TPFLAGS_DEFAULT - | $crate::_detail::ffi::Py_TPFLAGS_HAVE_GC - }; -} - -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 { @@ -93,57 +69,14 @@ macro_rules! py_class_type_object_dynamic_init { $setdelitem:tt } ) => { - unsafe { - $type_object.tp_name = $crate::py_class::slots::build_tp_name($module_name, stringify!($class)); - $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_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; - *(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; - } - - // 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); } } - -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)), - None => CString::new(type_name) - }; - name.expect("Module name/type name must not contain NUL byte").into_raw() -} - -pub unsafe extern "C" fn tp_dealloc_callback(obj: *mut ffi::PyObject) - where T: super::BaseObject -{ - let guard = ::function::AbortOnDrop("Cannot unwind out of tp_dealloc"); - let py = Python::assume_gil_acquired(); - let r = T::dealloc(py, obj); - mem::forget(guard); - r -} - #[macro_export] #[doc(hidden)] macro_rules! py_class_wrap_newfunc { @@ -203,22 +136,6 @@ macro_rules! py_class_as_number { }} } -#[macro_export] -#[doc(hidden)] -macro_rules! py_class_as_async { - ([]) => (0 as *mut $crate::_detail::ffi::PyAsyncMethods); - ([$( $slot_name:ident : $slot_value:expr ,)+]) => {{ - static mut ASYNC_METHODS : $crate::_detail::ffi::PyAsyncMethods - = $crate::_detail::ffi::PyAsyncMethods { - $( $slot_name : $slot_value, )* - .. - $crate::_detail::ffi::PyAsyncMethods_INIT - }; - unsafe { &mut ASYNC_METHODS } - }} -} - - #[macro_export] #[doc(hidden)] macro_rules! py_class_as_mapping {