pyo3/pyo3-derive-backend/src/py_class.rs

530 lines
19 KiB
Rust
Raw Normal View History

2017-05-16 05:24:06 +00:00
// Copyright (c) 2017-present PyO3 Project and Contributors
2017-06-09 19:30:13 +00:00
use std;
use std::collections::HashMap;
2017-05-16 05:24:06 +00:00
use syn;
2017-07-27 05:29:55 +00:00
use quote::{Tokens, ToTokens};
2017-05-16 05:24:06 +00:00
2017-06-13 00:15:26 +00:00
use utils;
2017-08-05 02:53:23 +00:00
use method::{FnType, FnSpec, FnArg};
use py_method::{impl_wrap_getter, impl_wrap_setter, impl_py_getter_def, impl_py_setter_def};
2017-06-13 00:15:26 +00:00
2017-05-16 05:24:06 +00:00
2017-06-09 19:30:13 +00:00
pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens {
2017-07-28 02:47:01 +00:00
let (params, flags, base) = parse_attribute(attr);
2017-06-18 15:00:27 +00:00
let doc = utils::get_doc(&ast.attrs, true);
2017-05-28 05:45:48 +00:00
let mut token: Option<syn::Ident> = None;
2017-08-05 02:53:23 +00:00
let mut descriptors = Vec::new();
2017-05-17 06:43:39 +00:00
match ast.body {
2017-05-28 05:45:48 +00:00
syn::Body::Struct(syn::VariantData::Struct(ref mut fields)) => {
2017-08-05 02:53:23 +00:00
for field in fields.iter_mut() {
2017-06-01 16:45:00 +00:00
if is_python_token(field) {
token = field.ident.clone();
break
2017-08-05 02:53:23 +00:00
} else {
let field_descs = parse_descriptors(field);
if !field_descs.is_empty() {
descriptors.push((field.clone(), field_descs));
}
2017-05-28 05:45:48 +00:00
}
}
},
2017-06-18 15:41:20 +00:00
_ => panic!("#[class] can only be used with normal structs"),
2017-05-17 06:43:39 +00:00
}
2017-05-16 05:24:06 +00:00
let dummy_const = syn::Ident::new(format!("_IMPL_PYO3_CLS_{}", ast.ident));
2017-08-05 02:53:23 +00:00
let tokens = impl_class(&ast.ident, &base, token, doc, params, flags, descriptors);
2017-05-16 05:24:06 +00:00
quote! {
#[allow(non_upper_case_globals, unused_attributes,
unused_qualifications, unused_variables, non_camel_case_types)]
2017-05-16 05:24:06 +00:00
const #dummy_const: () = {
2017-06-01 22:06:48 +00:00
use std;
use pyo3 as _pyo3;
2017-05-16 05:24:06 +00:00
#tokens
};
}
}
2017-08-05 02:53:23 +00:00
fn parse_descriptors(item: &mut syn::Field) -> Vec<FnType> {
let mut descs = Vec::new();
let mut new_attrs = Vec::new();
for attr in item.attrs.iter() {
match attr.value {
syn::MetaItem::List(ref name, ref metas) => {
match name.as_ref() {
"prop" => {
for meta in metas.iter() {
match *meta {
syn::NestedMetaItem::MetaItem(ref metaitem) => {
match metaitem.name() {
"get" => {
descs.push(FnType::Getter(None));
}
"set" => {
descs.push(FnType::Setter(None));
}
_ => {
panic!("Only getter and setter supported");
}
}
}
_ => ()
}
}
}
_ => {
new_attrs.push(attr.clone());
}
}
}
_ => {
new_attrs.push(attr.clone());
}
}
}
item.attrs.clear();
item.attrs.extend(new_attrs);
descs
}
2017-06-09 19:30:13 +00:00
fn impl_class(cls: &syn::Ident, base: &syn::Ident,
2017-06-13 00:15:26 +00:00
token: Option<syn::Ident>, doc: syn::Lit,
2017-08-05 02:53:23 +00:00
params: HashMap<&'static str, syn::Ident>,
flags: Vec<syn::Ident>,
descriptors: Vec<(syn::Field, Vec<FnType>)>) -> Tokens {
2017-06-09 19:30:13 +00:00
let cls_name = match params.get("name") {
Some(name) => quote! { #name }.as_str().to_string(),
None => quote! { #cls }.as_str().to_string()
};
2017-05-18 23:57:39 +00:00
2017-07-14 23:21:18 +00:00
let extra = if let Some(token) = token {
2017-05-28 05:45:48 +00:00
Some(quote! {
2017-06-01 22:06:48 +00:00
impl _pyo3::PyObjectWithToken for #cls {
2017-07-14 21:21:07 +00:00
#[inline(always)]
2017-07-14 02:04:00 +00:00
fn py<'p>(&'p self) -> _pyo3::Python<'p> {
2017-07-14 23:21:18 +00:00
self.#token.py()
2017-05-28 05:45:48 +00:00
}
}
2017-06-23 22:03:52 +00:00
impl _pyo3::ToPyObject for #cls {
#[inline]
fn to_object<'p>(&self, py: _pyo3::Python<'p>) -> _pyo3::PyObject {
unsafe { _pyo3::PyObject::from_borrowed_ptr(py, self.as_ptr()) }
}
}
impl _pyo3::ToBorrowedObject for #cls {
2017-06-23 22:03:52 +00:00
#[inline]
fn with_borrowed_ptr<F, R>(&self, _py: _pyo3::Python, f: F) -> R
2017-07-31 17:42:55 +00:00
where F: FnOnce(*mut _pyo3::ffi::PyObject) -> R
2017-06-23 22:03:52 +00:00
{
f(self.as_ptr())
}
}
impl<'a> _pyo3::ToPyObject for &'a mut #cls {
#[inline]
2017-06-23 03:56:09 +00:00
fn to_object<'p>(&self, py: _pyo3::Python<'p>) -> _pyo3::PyObject {
unsafe { _pyo3::PyObject::from_borrowed_ptr(py, self.as_ptr()) }
}
}
impl<'a> _pyo3::ToBorrowedObject for &'a mut #cls {
#[inline]
fn with_borrowed_ptr<F, R>(&self, _py: _pyo3::Python, f: F) -> R
2017-07-31 17:42:55 +00:00
where F: FnOnce(*mut _pyo3::ffi::PyObject) -> R
{
f(self.as_ptr())
}
}
2017-06-23 22:03:52 +00:00
impl<'a> std::convert::From<&'a mut #cls> for &'a #cls
{
fn from(ob: &'a mut #cls) -> Self {
unsafe{std::mem::transmute(ob)}
}
}
impl _pyo3::ToPyPointer for #cls {
#[inline]
2017-07-31 17:42:55 +00:00
fn as_ptr(&self) -> *mut _pyo3::ffi::PyObject {
unsafe {
{self as *const _ as *mut u8}
2017-07-31 17:42:55 +00:00
.offset(-<#cls as _pyo3::typeob::PyTypeInfo>::OFFSET) as *mut _pyo3::ffi::PyObject
}
}
}
2017-06-22 08:04:37 +00:00
impl std::fmt::Debug for #cls {
2017-05-28 05:45:48 +00:00
fn fmt(&self, f : &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
2017-06-22 08:04:37 +00:00
use pyo3::ObjectProtocol;
let s = try!(self.repr().map_err(|_| std::fmt::Error));
2017-06-21 22:11:32 +00:00
f.write_str(&s.to_string_lossy())
2017-05-28 05:45:48 +00:00
}
}
2017-06-22 08:04:37 +00:00
impl std::fmt::Display for #cls {
2017-05-28 05:45:48 +00:00
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
2017-06-22 08:04:37 +00:00
use pyo3::ObjectProtocol;
let s = try!(self.str().map_err(|_| std::fmt::Error));
2017-06-21 22:11:32 +00:00
f.write_str(&s.to_string_lossy())
2017-05-28 05:45:48 +00:00
}
2017-05-28 15:57:34 +00:00
}
2017-05-28 05:45:48 +00:00
})
} else {
None
};
2017-06-09 21:27:37 +00:00
let extra = {
if let Some(freelist) = params.get("freelist") {
Some(quote! {
impl _pyo3::freelist::PyObjectWithFreeList for #cls {
#[inline]
2017-07-18 17:53:02 +00:00
fn get_free_list() -> &'static mut _pyo3::freelist::FreeList<*mut _pyo3::ffi::PyObject> {
static mut FREELIST: *mut _pyo3::freelist::FreeList<*mut _pyo3::ffi::PyObject> = 0 as *mut _;
2017-06-09 21:27:37 +00:00
unsafe {
if FREELIST.is_null() {
FREELIST = Box::into_raw(Box::new(
_pyo3::freelist::FreeList::with_capacity(#freelist)));
2017-07-26 01:11:45 +00:00
<#cls as _pyo3::typeob::PyTypeObject>::init_type();
2017-06-09 21:27:37 +00:00
}
&mut *FREELIST
2017-06-09 21:27:37 +00:00
}
}
}
#extra
})
} else {
extra
}
};
2017-08-05 02:53:23 +00:00
let extra = if !descriptors.is_empty() {
let ty = syn::parse::ty(cls.as_ref()).expect("no name");
let desc_impls = impl_descriptors(&ty, descriptors);
Some(quote! {
#desc_impls
#extra
})
} else {
extra
};
2017-07-24 20:03:18 +00:00
// insert space for weak ref
let mut has_weakref = false;
2017-07-28 14:21:59 +00:00
let mut has_dict = false;
2017-07-24 20:03:18 +00:00
for f in flags.iter() {
if *f == syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_WEAKREF") {
has_weakref = true;
2017-07-28 14:21:59 +00:00
} else if *f == syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_DICT") {
has_dict = true;
2017-07-24 20:03:18 +00:00
}
}
let weakref = if has_weakref {
syn::Ident::from("std::mem::size_of::<*const _pyo3::ffi::PyObject>()")
} else {
syn::Ident::from("0")
};
2017-07-28 14:21:59 +00:00
let dict = if has_dict {
syn::Ident::from("std::mem::size_of::<*const _pyo3::ffi::PyObject>()")
} else {
syn::Ident::from("0")
};
2017-07-24 20:03:18 +00:00
2017-05-17 06:43:39 +00:00
quote! {
2017-05-25 05:43:07 +00:00
impl _pyo3::typeob::PyTypeInfo for #cls {
type Type = #cls;
2017-07-28 02:47:01 +00:00
type BaseType = #base;
2017-07-13 23:45:50 +00:00
const NAME: &'static str = #cls_name;
const DESCRIPTION: &'static str = #doc;
2017-07-27 05:29:55 +00:00
const FLAGS: usize = #(#flags)|*;
2017-07-28 02:47:01 +00:00
const SIZE: usize = {
Self::OFFSET as usize +
2017-07-28 14:21:59 +00:00
std::mem::size_of::<#cls>() + #weakref + #dict
2017-07-28 02:47:01 +00:00
};
const OFFSET: isize = {
// round base_size up to next multiple of align
(
(<#base as _pyo3::typeob::PyTypeInfo>::SIZE +
std::mem::align_of::<#cls>() - 1) /
std::mem::align_of::<#cls>() * std::mem::align_of::<#cls>()
) as isize
};
2017-05-17 06:43:39 +00:00
#[inline]
2017-07-13 23:45:50 +00:00
unsafe fn type_object() -> &'static mut _pyo3::ffi::PyTypeObject {
2017-05-18 18:15:06 +00:00
static mut TYPE_OBJECT: _pyo3::ffi::PyTypeObject = _pyo3::ffi::PyTypeObject_INIT;
2017-07-13 23:45:50 +00:00
&mut TYPE_OBJECT
2017-05-17 06:43:39 +00:00
}
}
2017-05-25 05:43:07 +00:00
2017-06-08 21:21:48 +00:00
impl _pyo3::typeob::PyTypeObject for #cls {
#[inline(always)]
2017-07-26 01:11:45 +00:00
fn init_type() {
2017-06-08 21:21:48 +00:00
static START: std::sync::Once = std::sync::ONCE_INIT;
START.call_once(|| {
2017-07-26 01:11:45 +00:00
let ty = unsafe{<#cls as _pyo3::typeob::PyTypeInfo>::type_object()};
2017-06-08 21:21:48 +00:00
if (ty.tp_flags & _pyo3::ffi::Py_TPFLAGS_READY) == 0 {
2017-07-26 01:11:45 +00:00
let gil = _pyo3::Python::acquire_gil();
let py = gil.python();
let error_message = "An error occurred while initializing class ".to_string() +
<#cls as _pyo3::typeob::PyTypeInfo>::NAME.as_ref();
2017-06-08 21:21:48 +00:00
// automatically initialize the class on-demand
2017-08-12 03:17:09 +00:00
_pyo3::typeob::initialize_type::<#cls>(py, None)
.map_err(|e| e.print(py))
.expect(&error_message);
2017-06-08 21:21:48 +00:00
}
});
}
}
2017-05-28 05:45:48 +00:00
#extra
2017-05-16 05:24:06 +00:00
}
}
2017-06-01 16:45:00 +00:00
2017-08-05 02:53:23 +00:00
fn impl_descriptors(cls: &syn::Ty, descriptors: Vec<(syn::Field, Vec<FnType>)>) -> Tokens {
let methods: Vec<Tokens> = descriptors.iter().flat_map(|&(ref field, ref fns)| {
fns.iter().map(|desc| {
let name = field.ident.clone().unwrap();
let field_ty = &field.ty;
match *desc {
FnType::Getter(_) => {
quote! {
impl #cls {
fn #name(&self) -> _pyo3::PyResult<#field_ty> {
Ok(self.#name)
}
}
}
}
FnType::Setter(_) => {
let setter_name = syn::Ident::from(format!("set_{}", name));
quote! {
impl #cls {
fn #setter_name(&mut self, value: #field_ty) -> _pyo3::PyResult<()> {
self.#name = value;
Ok(())
}
}
}
},
_ => unreachable!()
}
}).collect::<Vec<Tokens>>()
}).collect();
let py_methods: Vec<Tokens> = descriptors.iter().flat_map(|&(ref field, ref fns)| {
fns.iter().map(|desc| {
let name = field.ident.clone().unwrap();
// FIXME better doc?
let doc = syn::Lit::from(name.as_ref());
let field_ty = &field.ty;
match *desc {
FnType::Getter(ref getter) => {
impl_py_getter_def(&name, doc, getter, &impl_wrap_getter(&Box::new(cls.clone()), &name))
}
FnType::Setter(ref setter) => {
let mode = syn::BindingMode::ByValue(syn::Mutability::Immutable);
let setter_name = syn::Ident::from(format!("set_{}", name));
let spec = FnSpec {
tp: FnType::Setter(None),
attrs: Vec::new(),
args: vec![FnArg {
name: &name,
mode: &mode,
ty: field_ty,
optional: None,
py: true,
reference: false
}],
output: syn::parse::ty("PyResult<()>").expect("error parse PyResult<()>"),
2017-08-05 02:53:23 +00:00
};
impl_py_setter_def(&name, doc, setter, &impl_wrap_setter(&Box::new(cls.clone()), &setter_name, &spec))
},
_ => unreachable!()
}
}).collect::<Vec<Tokens>>()
}).collect();
let tokens = quote! {
#(#methods)*
2017-08-08 06:52:24 +00:00
impl _pyo3::class::methods::PyPropMethodsProtocolImpl for #cls {
2017-08-05 02:53:23 +00:00
fn py_methods() -> &'static [_pyo3::class::PyMethodDefType] {
static METHODS: &'static [_pyo3::class::PyMethodDefType] = &[
#(#py_methods),*
];
METHODS
}
}
};
let n = match cls {
&syn::Ty::Path(_, ref p) => {
p.segments.last().as_ref().unwrap().ident.as_ref()
}
_ => "CLS_METHODS"
};
let dummy_const = syn::Ident::new(format!("_IMPL_PYO3_DESCRIPTORS_{}", n));
quote! {
#[feature(specialization)]
#[allow(non_upper_case_globals, unused_attributes,
unused_qualifications, unused_variables, unused_imports)]
const #dummy_const: () = {
use pyo3 as _pyo3;
2017-08-05 02:53:23 +00:00
#tokens
};
}
}
2017-06-01 16:45:00 +00:00
fn is_python_token(field: &syn::Field) -> bool {
match field.ty {
syn::Ty::Path(_, ref path) => {
if let Some(segment) = path.segments.last() {
2017-06-01 22:06:48 +00:00
return segment.ident.as_ref() == "PyToken"
2017-06-01 16:45:00 +00:00
}
}
_ => (),
}
return false
}
2017-06-09 19:30:13 +00:00
2018-05-01 18:41:35 +00:00
fn parse_attribute(mut attr: String) -> (HashMap<&'static str, syn::Ident>,
Vec<syn::Ident>, syn::Ident) {
2017-06-09 19:30:13 +00:00
let mut params = HashMap::new();
let mut flags = vec![syn::Ident::from("0")];
2017-07-28 02:47:01 +00:00
let mut base = syn::Ident::from("_pyo3::PyObjectRef");
2017-06-09 19:30:13 +00:00
2018-05-01 18:41:35 +00:00
// https://github.com/rust-lang/rust/pull/50120 removed the parantheses from
// the attr TokenStream, so we need to re-add them manually
// Old nightly (like 2018-04-05): ( name=CustomName )
// New nightly (like 2018-04-28): name=CustomName
if attr.len() > 0 && !attr.starts_with("(") {
attr = format!("({})", attr);
}
2017-06-09 19:30:13 +00:00
if let Ok(tts) = syn::parse_token_trees(&attr) {
2017-06-09 19:35:29 +00:00
let mut elem = Vec::new();
2017-06-09 19:30:13 +00:00
let mut elems = Vec::new();
for tt in tts.iter() {
match tt {
2018-05-01 18:41:35 +00:00
&syn::TokenTree::Token(ref token) => {
println!("Wrong format: Expected delimited, found token: {:?} {:?}", attr.to_string(), token);
2017-06-09 19:30:13 +00:00
}
&syn::TokenTree::Delimited(ref delimited) => {
for tt in delimited.tts.iter() {
match tt {
&syn::TokenTree::Token(syn::Token::Comma) => {
let el = std::mem::replace(&mut elem, Vec::new());
elems.push(el);
},
_ => elem.push(tt.clone())
}
}
}
}
}
2017-06-09 19:35:29 +00:00
if !elem.is_empty() {
elems.push(elem);
}
2017-06-09 19:30:13 +00:00
for elem in elems {
let key = match elem[0] {
syn::TokenTree::Token(syn::Token::Ident(ref ident)) => {
ident.as_ref().to_owned().to_lowercase()
},
_ => {
2018-05-01 18:41:35 +00:00
println!("Wrong format: Expected Token: {:?}", attr.to_string());
2017-06-09 19:30:13 +00:00
continue
}
};
if elem.len() == 1 {
match key.as_ref() {
"gc" => {
flags.push(syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_GC"));
continue
}
"weakref" => {
flags.push(syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_WEAKREF"));
continue
}
2017-07-27 15:05:15 +00:00
"subclass" => {
flags.push(syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_BASETYPE"));
continue
}
2017-07-28 14:21:59 +00:00
"dict" => {
flags.push(syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_DICT"));
continue
}
_ => {
println!("Unsupported parameter: {:?}", key);
}
}
}
if elem.len() < 3 {
2018-05-01 18:41:35 +00:00
println!("Wrong format: Less than three elements{:?}", elem);
continue
}
2017-06-09 19:30:13 +00:00
match elem[1] {
syn::TokenTree::Token(syn::Token::Eq) => (),
_ => {
2018-05-01 18:41:35 +00:00
println!("Wrong format: Expected a Token as fist element: {:?}", attr.to_string());
2017-06-09 19:30:13 +00:00
continue
}
}
match key.as_ref() {
"freelist" => {
if elem.len() != 3 {
println!("Wrong 'freelist' format: {:?}", elem);
} else {
match elem[2] {
syn::TokenTree::Token(
syn::Token::Literal(
syn::Lit::Int(val, _))) => {
params.insert("freelist", syn::Ident::from(val.to_string()));
}
_ => println!("Wrong 'freelist' format: {:?}", elem)
}
}
},
"name" => {
if elem.len() != 3 {
println!("Wrong 'name' format: {:?}", elem);
} else {
match elem[2] {
syn::TokenTree::Token(syn::Token::Ident(ref ident)) => {
params.insert("name", ident.clone());
},
_ => println!("Wrong 'name' format: {:?}", elem)
}
}
},
"base" => {
2017-07-27 05:29:55 +00:00
let mut m = String::new();
2017-08-08 07:27:33 +00:00
for el in elem[2..elem.len()].iter() {
2017-07-27 05:29:55 +00:00
let mut t = Tokens::new();
el.to_tokens(&mut t);
m += t.as_str().trim();
}
2017-07-28 02:47:01 +00:00
base = syn::Ident::from(m.as_str());
},
2017-06-09 19:30:13 +00:00
_ => {
println!("Unsupported parameter: {:?}", key);
}
}
}
}
2017-07-27 05:29:55 +00:00
(params, flags, base)
2017-06-09 19:30:13 +00:00
}