Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New #[pyclass] system that is aware of its concrete layout #683

Merged
merged 27 commits into from
Jan 11, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4b5fa7e
Introduce PyClass trait and PyClassShell
kngwyu Dec 7, 2019
bdb66af
Make PyClassShell have dict&weakref
kngwyu Dec 8, 2019
4d7dfaf
Allow slf: &PyClassShell<Self>
kngwyu Dec 8, 2019
a663907
Introduce PyInternalCaster
kngwyu Dec 14, 2019
b86de93
Introduce PyClassInitializer
kngwyu Dec 15, 2019
8175d6f
Merge branch 'master' into pyclass-new-layout
kngwyu Dec 19, 2019
6b84401
Make it enable to safely inherit native types
kngwyu Dec 21, 2019
efa16a6
Fix documents accompanied by PyClassShell
kngwyu Dec 22, 2019
e2dc843
Fix a corner case for PyClassInitializer
kngwyu Dec 22, 2019
acb1120
Fix examples with the new #[new] API
kngwyu Dec 22, 2019
d5cff05
Fix documents and a clippy warning
kngwyu Dec 22, 2019
ea51756
Resolve some clippy complains
kngwyu Dec 23, 2019
2e3ece8
Try to enhance class section in the guide
kngwyu Dec 23, 2019
5859039
Fix accidently changed file permission
kngwyu Dec 24, 2019
766a520
Documentation enhancement
kngwyu Dec 28, 2019
8f8785d
Merge branch 'master' into pyclass-new-layout
kngwyu Dec 29, 2019
18e565f
New PyClassInitializer
kngwyu Jan 5, 2020
60edeb8
Simplify IntoInitializer
davidhewitt Jan 6, 2020
b04d0af
Merge pull request #1 from davidhewitt/pyclass-new-layout
kngwyu Jan 7, 2020
b602b4b
Enhance documentation and tests around #[new]
kngwyu Jan 7, 2020
f26e07c
Replace IntoInitializer<T> with Into<PyClassInitializer<T>>
kngwyu Jan 7, 2020
67a98d6
Remove unnecessary Box
kngwyu Jan 7, 2020
ab0a731
Fix use order in prelude
kngwyu Jan 7, 2020
451de18
Merge branch 'master' into pyclass-new-layout
kngwyu Jan 8, 2020
c57177a
Refine tests and documents around pyclass.rs
kngwyu Jan 8, 2020
302b3bb
Merge branch 'master' into pyclass-new-layout
kngwyu Jan 11, 2020
439efbb
Update CHANGELOG
kngwyu Jan 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions pyo3-derive-backend/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub enum FnType {
FnCall,
FnClass,
FnStatic,
PySelf(syn::TypePath),
PySelfNew(syn::TypeReference),
}

#[derive(Clone, PartialEq, Debug)]
Expand Down Expand Up @@ -65,7 +65,7 @@ impl<'a> FnSpec<'a> {
ref pat, ref ty, ..
}) => {
// skip first argument (cls)
if (fn_type == FnType::FnClass || fn_type == FnType::FnNew) && !has_self {
if fn_type == FnType::FnClass && !has_self {
has_self = true;
continue;
}
Expand Down Expand Up @@ -103,13 +103,16 @@ impl<'a> FnSpec<'a> {

if fn_type == FnType::Fn && !has_self {
if arguments.is_empty() {
panic!("Static method needs #[staticmethod] attribute");
return Err(syn::Error::new_spanned(
name,
"Static method needs #[staticmethod] attribute",
));
}
let tp = match arguments.remove(0).ty {
syn::Type::Path(p) => replace_self(p),
_ => panic!("Invalid type as self"),
syn::Type::Reference(r) => replace_self(r)?,
x => return Err(syn::Error::new_spanned(x, "Invalid type as custom self")),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this code now won't support this: Py<Self> as a self type, which seems like a reasonable thing for a user to want to try.

};
fn_type = FnType::PySelf(tp);
fn_type = FnType::PySelfNew(tp);
}

Ok(FnSpec {
Expand Down Expand Up @@ -386,15 +389,19 @@ fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<(FnType, Vec
}
}

// Replace A<Self> with A<_>
fn replace_self(path: &syn::TypePath) -> syn::TypePath {
// Replace &A<Self> with &A<_>
fn replace_self(refn: &syn::TypeReference) -> syn::Result<syn::TypeReference> {
fn infer(span: proc_macro2::Span) -> syn::GenericArgument {
syn::GenericArgument::Type(syn::Type::Infer(syn::TypeInfer {
underscore_token: syn::token::Underscore { spans: [span] },
}))
}
let mut res = path.to_owned();
for seg in &mut res.path.segments {
let mut res = refn.to_owned();
let tp = match &mut *res.elem {
syn::Type::Path(p) => p,
_ => return Err(syn::Error::new_spanned(refn, "unsupported argument")),
};
for seg in &mut tp.path.segments {
if let syn::PathArguments::AngleBracketed(ref mut g) = seg.arguments {
let mut args = syn::punctuated::Punctuated::new();
for arg in &g.args {
Expand All @@ -415,5 +422,6 @@ fn replace_self(path: &syn::TypePath) -> syn::TypePath {
g.args = args;
}
}
res
res.lifetime = None;
Ok(res)
}
46 changes: 20 additions & 26 deletions pyo3-derive-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@ impl PyClassArgs {
let flag = exp.path.segments.first().unwrap().ident.to_string();
let path = match flag.as_str() {
"gc" => {
parse_quote! {pyo3::type_object::PY_TYPE_FLAG_GC}
parse_quote! {pyo3::type_flags::GC}
}
"weakref" => {
parse_quote! {pyo3::type_object::PY_TYPE_FLAG_WEAKREF}
parse_quote! {pyo3::type_flags::WEAKREF}
}
"subclass" => {
if cfg!(not(feature = "unsound-subclass")) {
Expand All @@ -138,10 +138,10 @@ impl PyClassArgs {
"You need to activate the `unsound-subclass` feature if you want to use subclassing",
));
}
parse_quote! {pyo3::type_object::PY_TYPE_FLAG_BASETYPE}
parse_quote! {pyo3::type_flags::BASETYPE}
}
"dict" => {
parse_quote! {pyo3::type_object::PY_TYPE_FLAG_DICT}
parse_quote! {pyo3::type_flags::DICT}
}
_ => {
return Err(syn::Error::new_spanned(
Expand Down Expand Up @@ -267,7 +267,7 @@ fn impl_class(
let extra = {
if let Some(freelist) = &attr.freelist {
quote! {
impl pyo3::freelist::PyObjectWithFreeList for #cls {
impl pyo3::freelist::PyClassWithFreeList for #cls {
#[inline]
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 _;
Expand All @@ -285,7 +285,7 @@ fn impl_class(
}
} else {
quote! {
impl pyo3::type_object::PyObjectAlloc for #cls {}
impl pyo3::pyclass::PyClassAlloc for #cls {}
}
}
};
Expand All @@ -308,24 +308,25 @@ fn impl_class(
let mut has_gc = false;
for f in attr.flags.iter() {
if let syn::Expr::Path(ref epath) = f {
if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_WEAKREF} {
if epath.path == parse_quote! { pyo3::type_flags::WEAKREF } {
has_weakref = true;
} else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_DICT} {
} else if epath.path == parse_quote! { pyo3::type_flags::DICT } {
has_dict = true;
} else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_GC} {
} else if epath.path == parse_quote! { pyo3::type_flags::GC } {
has_gc = true;
}
}
}
// TODO: implement dict and weakref
let weakref = if has_weakref {
quote! {std::mem::size_of::<*const pyo3::ffi::PyObject>()}
quote! { type WeakRef = pyo3::pyclass_slots::PyClassWeakRefSlot; }
} else {
quote! {0}
quote! { type WeakRef = pyo3::pyclass_slots::PyClassDummySlot; }
};
let dict = if has_dict {
quote! {std::mem::size_of::<*const pyo3::ffi::PyObject>()}
quote! { type Dict = pyo3::pyclass_slots::PyClassDictSlot; }
} else {
quote! {0}
quote! { type Dict = pyo3::pyclass_slots::PyClassDummySlot; }
};
let module = if let Some(m) = &attr.module {
quote! { Some(#m) }
Expand Down Expand Up @@ -358,32 +359,25 @@ fn impl_class(
impl pyo3::type_object::PyTypeInfo for #cls {
type Type = #cls;
type BaseType = #base;
type ConcreteLayout = pyo3::pyclass::PyClassShell<Self>;

const NAME: &'static str = #cls_name;
const MODULE: Option<&'static str> = #module;
const DESCRIPTION: &'static str = #doc;
const FLAGS: usize = #(#flags)|*;

const SIZE: usize = {
Self::OFFSET as usize +
::std::mem::size_of::<#cls>() + #weakref + #dict
};
const OFFSET: isize = {
// round base_size up to next multiple of align
(
(<#base as pyo3::type_object::PyTypeInfo>::SIZE +
::std::mem::align_of::<#cls>() - 1) /
::std::mem::align_of::<#cls>() * ::std::mem::align_of::<#cls>()
) as isize
};

#[inline]
unsafe fn type_object() -> &'static mut pyo3::ffi::PyTypeObject {
static mut TYPE_OBJECT: pyo3::ffi::PyTypeObject = pyo3::ffi::PyTypeObject_INIT;
&mut TYPE_OBJECT
}
}

impl pyo3::PyClass for #cls {
#dict
#weakref
}

impl pyo3::IntoPy<PyObject> for #cls {
fn into_py(self, py: pyo3::Python) -> pyo3::PyObject {
pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py)
Expand Down
56 changes: 28 additions & 28 deletions pyo3-derive-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub fn gen_py_method(
};

let text_signature = match &spec.tp {
FnType::Fn | FnType::PySelf(_) | FnType::FnClass | FnType::FnStatic => {
FnType::Fn | FnType::PySelfNew(_) | FnType::FnClass | FnType::FnStatic => {
utils::parse_text_signature_attrs(&mut *meth_attrs, name)?
}
FnType::FnNew => parse_erroneous_text_signature(
Expand All @@ -59,7 +59,7 @@ pub fn gen_py_method(

Ok(match spec.tp {
FnType::Fn => impl_py_method_def(name, doc, &spec, &impl_wrap(cls, name, &spec, true)),
FnType::PySelf(ref self_ty) => impl_py_method_def(
FnType::PySelfNew(ref self_ty) => impl_py_method_def(
name,
doc,
&spec,
Expand Down Expand Up @@ -127,7 +127,7 @@ pub fn impl_wrap_pyslf(
cls: &syn::Type,
name: &syn::Ident,
spec: &FnSpec<'_>,
self_ty: &syn::TypePath,
self_ty: &syn::TypeReference,
noargs: bool,
) -> TokenStream {
let names = get_arg_names(spec);
Expand Down Expand Up @@ -221,9 +221,12 @@ pub fn impl_proto_wrap(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) ->
/// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap_new(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> TokenStream {
let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { #cls::#name(&_obj, #(#names),*) };

let body = impl_arg_params(spec, cb);
let cb = quote! { #cls::#name(#(#names),*) };
let body = impl_arg_params_(
spec,
cb,
quote! { pyo3::pyclass::IntoInitializer::into_initializer },
);

quote! {
#[allow(unused_mut)]
Expand All @@ -237,25 +240,14 @@ pub fn impl_wrap_new(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> T
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#name),"()");
let _py = pyo3::Python::assume_gil_acquired();
let _pool = pyo3::GILPool::new(_py);
match pyo3::type_object::PyRawObject::new(_py, #cls::type_object(), _cls) {
Ok(_obj) => {
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);

#body

match _result {
Ok(_) => pyo3::IntoPyPointer::into_ptr(_obj),
Err(e) => {
e.restore(_py);
::std::ptr::null_mut()
}
}
}
Err(e) => {
e.restore(_py);
::std::ptr::null_mut()
}
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);

#body

match _result.and_then(|init| init.create_shell(_py)) {
Ok(slf) => slf as _,
Err(e) => e.restore_and_null(_py),
}
}
}
Expand Down Expand Up @@ -421,11 +413,11 @@ fn bool_to_ident(condition: bool) -> syn::Ident {
}
}

pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {
fn impl_arg_params_(spec: &FnSpec<'_>, body: TokenStream, into_result: TokenStream) -> TokenStream {
if spec.args.is_empty() {
return quote! {
let _result = {
pyo3::derive_utils::IntoPyResult::into_py_result(#body)
#into_result (#body)
};
};
}
Expand Down Expand Up @@ -483,11 +475,19 @@ pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {

#(#param_conversion)*

pyo3::derive_utils::IntoPyResult::into_py_result(#body)
#into_result(#body)
})();
}
}

pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {
impl_arg_params_(
spec,
body,
quote! { pyo3::derive_utils::IntoPyResult::into_py_result },
)
}

/// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument
/// index and the index in option diverge when using py: Python
fn impl_arg_param(
Expand Down
13 changes: 5 additions & 8 deletions src/class/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,24 @@

use crate::callback::{CallbackConverter, PyObjectCallbackConverter};
use crate::err::PyResult;
use crate::instance::PyRefMut;
use crate::type_object::PyTypeInfo;
use crate::IntoPyPointer;
use crate::Python;
use crate::{ffi, IntoPy, PyObject};
use crate::{ffi, pyclass::PyClassShell, IntoPy, PyClass, PyObject};
use crate::{IntoPyPointer, Python};
use std::ptr;

/// Python Iterator Interface.
///
/// more information
/// `https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_iter`
#[allow(unused_variables)]
pub trait PyIterProtocol<'p>: PyTypeInfo + Sized {
fn __iter__(slf: PyRefMut<'p, Self>) -> Self::Result
pub trait PyIterProtocol<'p>: PyClass {
fn __iter__(slf: &mut PyClassShell<Self>) -> Self::Result
where
Self: PyIterIterProtocol<'p>,
{
unimplemented!()
}

fn __next__(slf: PyRefMut<'p, Self>) -> Self::Result
fn __next__(slf: &mut PyClassShell<Self>) -> Self::Result
where
Self: PyIterNextProtocol<'p>,
{
Expand Down
6 changes: 3 additions & 3 deletions src/class/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ macro_rules! py_unary_pyref_func {
where
T: for<'p> $trait<'p>,
{
use $crate::instance::PyRefMut;
use $crate::pyclass::PyClassShell;
let py = $crate::Python::assume_gil_acquired();
let _pool = $crate::GILPool::new(py);
let slf = py.mut_from_borrowed_ptr::<T>(slf);
let res = $class::$f(PyRefMut::from_mut(slf)).into();
let slf: &mut PyClassShell<T> = &mut *(slf as *mut PyClassShell<T>);
let res = $class::$f(slf).into();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if if would be nice to do something like

let res = $class::$f(slf.try_into()).into();

And then add a few conversions to allow e.g. &mut PyClassShell<T> -> &mut T, &mut PyClassShell<T> -> &T etc?

$crate::callback::cb_convert($conv, py, res)
}
Some(wrap::<$class>)
Expand Down
Loading