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

463 lines
17 KiB
Rust
Raw Normal View History

2017-05-16 05:24:06 +00:00
// Copyright (c) 2017-present PyO3 Project and Contributors
2018-07-03 20:28:40 +00:00
use method::{FnArg, FnSpec, FnType};
use proc_macro2::{Span, TokenStream};
use py_method::{impl_py_getter_def, impl_py_setter_def, impl_wrap_getter, impl_wrap_setter};
2018-07-04 17:07:27 +00:00
use std::collections::HashMap;
use syn;
2017-06-13 00:15:26 +00:00
use utils;
2018-07-04 17:07:27 +00:00
pub fn build_py_class(class: &mut syn::ItemStruct, attr: &Vec<syn::Expr>) -> TokenStream {
2017-07-28 02:47:01 +00:00
let (params, flags, base) = parse_attribute(attr);
2018-07-04 17:07:27 +00:00
let doc = utils::get_doc(&class.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();
2018-07-04 17:07:27 +00:00
if let syn::Fields::Named(ref mut fields) = class.fields {
for field in fields.named.iter_mut() {
if is_python_token(field) {
if token.is_none() {
token = field.ident.clone();
} else {
panic!("You can only have one PyToken per class");
}
2018-07-04 17:07:27 +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
}
}
}
} else {
panic!("#[pyclass] can only be used with C-style structs")
2017-05-17 06:43:39 +00:00
}
2018-07-04 17:07:27 +00:00
impl_class(&class.ident, &base, token, doc, params, flags, descriptors)
2017-05-16 05:24:06 +00:00
}
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() {
if let Some(syn::Meta::List(ref list)) = attr.interpret_meta() {
match list.ident.to_string().as_str() {
"prop" => {
for meta in list.nested.iter() {
if let &syn::NestedMeta::Meta(ref metaitem) = meta {
match metaitem.name().to_string().as_str() {
"get" => {
descs.push(FnType::Getter(None));
}
"set" => {
descs.push(FnType::Setter(None));
}
x => {
panic!(r#"Only "get" and "set" supported are, not "{}""#, x);
2017-08-05 02:53:23 +00:00
}
}
}
}
}
_ => new_attrs.push(attr.clone()),
2017-08-05 02:53:23 +00:00
}
} else {
new_attrs.push(attr.clone());
2017-08-05 02:53:23 +00:00
}
}
item.attrs.clear();
item.attrs.extend(new_attrs);
descs
}
fn impl_class(
cls: &syn::Ident,
base: &syn::TypePath,
token: Option<syn::Ident>,
doc: syn::Lit,
params: HashMap<&'static str, syn::Expr>,
flags: Vec<syn::Expr>,
2018-07-03 20:28:40 +00:00
descriptors: Vec<(syn::Field, Vec<FnType>)>,
) -> TokenStream {
2017-06-09 19:30:13 +00:00
let cls_name = match params.get("name") {
Some(name) => quote! { #name }.to_string(),
2018-07-03 20:28:40 +00:00
None => quote! { #cls }.to_string(),
2017-06-09 19:30:13 +00:00
};
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! {
2018-06-15 20:50:26 +00:00
impl ::pyo3::PyObjectWithToken for #cls {
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<'a> std::convert::From<&'a mut #cls> for &'a #cls
{
fn from(ob: &'a mut #cls) -> Self {
unsafe{std::mem::transmute(ob)}
}
}
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> {
2018-08-11 15:35:03 +00:00
use ::pyo3::ObjectProtocol;
let s = 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> {
2018-08-11 15:35:03 +00:00
use ::pyo3::ObjectProtocol;
let s = 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! {
2018-06-15 20:50:26 +00:00
impl ::pyo3::freelist::PyObjectWithFreeList for #cls {
2017-06-09 21:27:37 +00:00
#[inline]
2018-06-15 20:50:26 +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(
2018-06-15 20:50:26 +00:00
::pyo3::freelist::FreeList::with_capacity(#freelist)));
2017-06-09 21:27:37 +00:00
2018-06-15 20:50:26 +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_str(&cls.to_string()).expect("no name");
2017-08-05 02:53:23 +00:00
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 let syn::Expr::Path(ref epath) = f {
2018-07-04 17:07:27 +00:00
if epath.path == parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_WEAKREF} {
has_weakref = true;
2018-07-04 17:07:27 +00:00
} else if epath.path == parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_DICT} {
has_dict = true;
}
2017-07-24 20:03:18 +00:00
}
}
let weakref = if has_weakref {
2018-07-04 17:07:27 +00:00
quote! {std::mem::size_of::<*const ::pyo3::ffi::PyObject>()}
2017-07-24 20:03:18 +00:00
} else {
2018-07-04 17:07:27 +00:00
quote! {0}
2017-07-24 20:03:18 +00:00
};
2017-07-28 14:21:59 +00:00
let dict = if has_dict {
2018-07-04 17:07:27 +00:00
quote! {std::mem::size_of::<*const ::pyo3::ffi::PyObject>()}
2017-07-28 14:21:59 +00:00
} else {
2018-07-04 17:07:27 +00:00
quote! {0}
2017-07-28 14:21:59 +00:00
};
2017-07-24 20:03:18 +00:00
2017-05-17 06:43:39 +00:00
quote! {
2018-06-15 20:50:26 +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 +
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
(
2018-06-15 20:50:26 +00:00
(<#base as ::pyo3::typeob::PyTypeInfo>::SIZE +
2017-07-28 02:47:01 +00:00
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]
2018-06-15 20:50:26 +00:00
unsafe fn type_object() -> &'static mut ::pyo3::ffi::PyTypeObject {
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
2018-06-15 20:50:26 +00:00
impl ::pyo3::typeob::PyTypeObject for #cls {
#[inline]
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(|| {
2018-06-15 20:50:26 +00:00
let ty = unsafe{<#cls as ::pyo3::typeob::PyTypeInfo>::type_object()};
2017-06-08 21:21:48 +00:00
2018-06-15 20:50:26 +00:00
if (ty.tp_flags & ::pyo3::ffi::Py_TPFLAGS_READY) == 0 {
let gil = ::pyo3::Python::acquire_gil();
2017-07-26 01:11:45 +00:00
let py = gil.python();
2017-06-08 21:21:48 +00:00
// automatically initialize the class on-demand
2018-06-15 20:50:26 +00:00
::pyo3::typeob::initialize_type::<#cls>(py, None)
2017-08-12 03:17:09 +00:00
.map_err(|e| e.print(py))
.expect(format!("An error occurred while initializing class {}",
2018-06-15 20:50:26 +00:00
<#cls as ::pyo3::typeob::PyTypeInfo>::NAME).as_ref());
2017-06-08 21:21:48 +00:00
}
});
}
}
// TBH I'm not sure what exactely this does and I'm sure there's a better way,
// but for now it works and it only safe code and it is required to return custom
// objects, so for now I'm keeping it
impl ::pyo3::IntoPyObject for #cls {
fn into_object<'p>(self, py: ::pyo3::Python<'p>) -> ::pyo3::PyObject {
::pyo3::Py::new(py, |_| self).unwrap().into_object(py)
}
}
impl ::pyo3::ToPyObject for #cls {
fn to_object<'p>(&self, py: ::pyo3::Python<'p>) -> ::pyo3::PyObject {
unsafe { ::pyo3::PyObject::from_borrowed_ptr(py, self.as_ptr()) }
}
}
impl ::pyo3::ToPyPointer for #cls {
fn as_ptr(&self) -> *mut ::pyo3::ffi::PyObject {
unsafe {
{self as *const _ as *mut u8}
.offset(-<#cls as ::pyo3::typeob::PyTypeInfo>::OFFSET) as *mut ::pyo3::ffi::PyObject
}
}
}
impl<'a> ::pyo3::ToPyObject for &'a mut #cls {
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 {
fn with_borrowed_ptr<F, R>(&self, _py: ::pyo3::Python, f: F) -> R
where F: FnOnce(*mut ::pyo3::ffi::PyObject) -> R
{
f(self.as_ptr())
}
}
impl<'a> ::pyo3::ToBorrowedObject for &'a mut #cls {
fn with_borrowed_ptr<F, R>(&self, _py: ::pyo3::Python, f: F) -> R
where F: FnOnce(*mut ::pyo3::ffi::PyObject) -> R
{
f(self.as_ptr())
}
}
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
2018-07-03 20:28:40 +00:00
fn impl_descriptors(cls: &syn::Type, descriptors: Vec<(syn::Field, Vec<FnType>)>) -> TokenStream {
let methods: Vec<TokenStream> = 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.clone())
}
}
2017-08-05 02:53:23 +00:00
}
}
2018-07-03 20:28:40 +00:00
FnType::Setter(_) => {
let setter_name =
syn::Ident::new(&format!("set_{}", name), Span::call_site());
quote! {
impl #cls {
fn #setter_name(&mut self, value: #field_ty) -> ::pyo3::PyResult<()> {
self.#name = value;
Ok(())
}
}
2017-08-05 02:53:23 +00:00
}
}
2018-07-03 20:28:40 +00:00
_ => unreachable!(),
2017-08-05 02:53:23 +00:00
}
2018-07-03 20:28:40 +00:00
})
.collect::<Vec<TokenStream>>()
})
.collect();
let py_methods: Vec<TokenStream> = 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 = syn::parse_str(&format!("\"{}\"", name)).unwrap();
let field_ty = &field.ty;
match *desc {
FnType::Getter(ref getter) => {
impl_py_getter_def(&name, doc, getter, &impl_wrap_getter(&cls, &name))
}
FnType::Setter(ref setter) => {
let setter_name =
syn::Ident::new(&format!("set_{}", name), Span::call_site());
let spec = FnSpec {
tp: FnType::Setter(None),
attrs: Vec::new(),
args: vec![FnArg {
name: &name,
mutability: &None,
by_ref: &None,
ty: field_ty,
optional: None,
py: true,
reference: false,
}],
output: parse_quote!(PyResult<()>),
};
impl_py_setter_def(
&name,
doc,
setter,
&impl_wrap_setter(&cls, &setter_name, &spec),
)
}
_ => unreachable!(),
}
}).collect::<Vec<TokenStream>>()
}).collect();
2017-08-05 02:53:23 +00:00
2018-07-03 19:11:56 +00:00
quote! {
2017-08-05 02:53:23 +00:00
#(#methods)*
2018-06-15 20:50:26 +00:00
impl ::pyo3::class::methods::PyPropMethodsProtocolImpl for #cls {
fn py_methods() -> &'static [::pyo3::class::PyMethodDefType] {
static METHODS: &'static [::pyo3::class::PyMethodDefType] = &[
2017-08-05 02:53:23 +00:00
#(#py_methods),*
];
METHODS
}
}
}
}
2017-06-01 16:45:00 +00:00
fn is_python_token(field: &syn::Field) -> bool {
match field.ty {
syn::Type::Path(ref typath) => {
if let Some(segment) = typath.path.segments.last() {
2018-07-03 20:28:40 +00:00
return segment.value().ident.to_string() == "PyToken";
2017-06-01 16:45:00 +00:00
}
}
_ => (),
}
2018-07-03 20:28:40 +00:00
return false;
2017-06-01 16:45:00 +00:00
}
2017-06-09 19:30:13 +00:00
fn parse_attribute(
args: &Vec<syn::Expr>,
) -> (
HashMap<&'static str, syn::Expr>,
Vec<syn::Expr>,
2018-07-03 20:28:40 +00:00
syn::TypePath,
) {
2017-06-09 19:30:13 +00:00
let mut params = HashMap::new();
2018-09-02 21:33:45 +00:00
// We need the 0 as value for the constant we're later building using quote for when there
// are no other flags
2018-09-02 21:13:35 +00:00
let mut flags = vec![parse_quote! {0}];
2018-07-04 17:07:27 +00:00
let mut base: syn::TypePath = parse_quote! {::pyo3::PyObjectRef};
2017-06-09 19:30:13 +00:00
for expr in args.iter() {
match expr {
// Match a single flag
2018-09-02 21:33:45 +00:00
syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => {
let flag = exp.path.segments.first().unwrap().value().ident.to_string();
let path = match flag.as_str() {
"gc" => {
parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_GC}
}
"weakref" => {
parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_WEAKREF}
}
"subclass" => {
parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_BASETYPE}
}
"dict" => {
parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_DICT}
}
param => panic!("Unsupported parameter: {}", param),
};
flags.push(syn::Expr::Path(path));
}
// Match a key/value flag
syn::Expr::Assign(ref ass) => {
let key = match *ass.left {
syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => {
exp.path.segments.first().unwrap().value().ident.to_string()
2017-06-09 19:30:13 +00:00
}
2018-07-03 20:28:40 +00:00
_ => panic!("could not parse argument: {:?}", ass),
};
match key.as_str() {
"freelist" => {
// TODO: check if int literal
params.insert("freelist", *ass.right.clone());
2018-07-03 20:28:40 +00:00
}
"name" => match *ass.right {
syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => {
params.insert("name", exp.clone().into());
}
2018-09-02 21:33:45 +00:00
_ => panic!("Wrong 'name' format: {:?}", *ass.right),
},
"extends" => match *ass.right {
2018-07-03 20:28:40 +00:00
syn::Expr::Path(ref exp) => {
base = syn::TypePath {
path: exp.path.clone(),
qself: None,
};
2017-06-09 19:30:13 +00:00
}
2018-09-02 21:33:45 +00:00
_ => panic!("Wrong 'base' format: {:?}", *ass.right),
2018-07-03 20:28:40 +00:00
},
_ => {
2018-09-02 21:33:45 +00:00
panic!("Unsupported parameter: {:?}", key);
2017-07-27 05:29:55 +00:00
}
2017-06-09 19:30:13 +00:00
}
}
_ => panic!("could not parse arguments"),
2017-06-09 19:30:13 +00:00
}
}
2017-07-27 05:29:55 +00:00
(params, flags, base)
2017-06-09 19:30:13 +00:00
}