-
Notifications
You must be signed in to change notification settings - Fork 770
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
Rename PyClassShell with PyCell
and do mutability checking
#770
Changes from 7 commits
a2408ca
5d4e737
c43fb9f
4a5f219
da4ed39
8f8b425
2fd2185
daca04e
98d810e
4b746af
1f5cb83
3d0ee2a
043b130
c2a40fb
d3d61c6
0e3f7cb
68a3b15
9bc4192
1895715
6c652df
bab146a
ff1ae98
0515941
6a64806
dcac5ad
461b32a
399e4bf
ca6227c
ee0c178
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ impl pyo3::pyclass::PyClassAlloc for MyClass {} | |
unsafe impl pyo3::PyTypeInfo for MyClass { | ||
type Type = MyClass; | ||
type BaseType = pyo3::types::PyAny; | ||
type ConcreteLayout = pyo3::PyClassShell<Self>; | ||
type ConcreteLayout = pyo3::PyCell<Self>; | ||
type Initializer = pyo3::PyClassInitializer<Self>; | ||
|
||
const NAME: &'static str = "MyClass"; | ||
|
@@ -109,35 +109,35 @@ fn mymodule(_py: Python, m: &PyModule) -> PyResult<()> { | |
You sometimes need to convert your `pyclass` into a Python object in Rust code (e.g., for testing it). | ||
|
||
For getting *GIL-bounded* (i.e., with `'py` lifetime) references of `pyclass`, | ||
you can use `PyClassShell<T>`. | ||
you can use `PyCell<T>`. | ||
Or you can use `Py<T>` directly, for *not-GIL-bounded* references. | ||
|
||
### `PyClassShell` | ||
`PyClassShell` represents the actual layout of `pyclass` on the Python heap. | ||
### `PyCell` | ||
`PyCell` represents the actual layout of `pyclass` on the Python heap. | ||
|
||
If you want to instantiate `pyclass` in Python and get the reference, | ||
you can use `PyClassShell::new_ref` or `PyClassShell::new_mut`. | ||
you can use `PyCell::new_ref` or `PyCell::new_mut`. | ||
|
||
```rust | ||
# use pyo3::prelude::*; | ||
# use pyo3::types::PyDict; | ||
# use pyo3::PyClassShell; | ||
# use pyo3::PyCell; | ||
#[pyclass] | ||
struct MyClass { | ||
num: i32, | ||
debug: bool, | ||
} | ||
let gil = Python::acquire_gil(); | ||
let py = gil.python(); | ||
let obj = PyClassShell::new_ref(py, MyClass { num: 3, debug: true }).unwrap(); | ||
let obj = PyCell::new_ref(py, MyClass { num: 3, debug: true }).unwrap(); | ||
// You can use deref | ||
assert_eq!(obj.num, 3); | ||
let dict = PyDict::new(py); | ||
// You can treat a `&PyClassShell` as a normal Python object | ||
// You can treat a `&PyCell` as a normal Python object | ||
dict.set_item("obj", obj).unwrap(); | ||
|
||
// return &mut PyClassShell<MyClass> | ||
let obj = PyClassShell::new_mut(py, MyClass { num: 3, debug: true }).unwrap(); | ||
// return &mut PyCell<MyClass> | ||
let obj = PyCell::new_mut(py, MyClass { num: 3, debug: true }).unwrap(); | ||
obj.num = 5; | ||
``` | ||
|
||
|
@@ -230,7 +230,7 @@ explicitly. | |
|
||
```rust | ||
# use pyo3::prelude::*; | ||
use pyo3::PyClassShell; | ||
use pyo3::PyCell; | ||
|
||
#[pyclass] | ||
struct BaseClass { | ||
|
@@ -261,7 +261,7 @@ impl SubClass { | |
(SubClass{ val2: 15}, BaseClass::new()) | ||
} | ||
|
||
fn method2(self_: &PyClassShell<Self>) -> PyResult<usize> { | ||
fn method2(self_: &PyCell<Self>) -> PyResult<usize> { | ||
self_.get_super().method().map(|x| x * self_.val2) | ||
} | ||
} | ||
|
@@ -279,7 +279,7 @@ impl SubSubClass { | |
.add_subclass(SubSubClass{val3: 20}) | ||
} | ||
|
||
fn method3(self_: &PyClassShell<Self>) -> PyResult<usize> { | ||
fn method3(self_: &PyCell<Self>) -> PyResult<usize> { | ||
let super_ = self_.get_super(); | ||
SubClass::method2(super_).map(|x| x * self_.val3) | ||
} | ||
|
@@ -288,20 +288,20 @@ impl SubSubClass { | |
|
||
# let gil = Python::acquire_gil(); | ||
# let py = gil.python(); | ||
# let subsub = pyo3::PyClassShell::new_ref(py, SubSubClass::new()).unwrap(); | ||
# let subsub = pyo3::PyCell::new_ref(py, SubSubClass::new()).unwrap(); | ||
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000") | ||
``` | ||
|
||
To access the super class, you can use either of these two ways: | ||
- Use `self_: &PyClassShell<Self>` instead of `self`, and call `get_super()` | ||
- Use `self_: &PyCell<Self>` instead of `self`, and call `get_super()` | ||
- `ObjectProtocol::get_base` | ||
We recommend `PyClassShell` here, since it makes the context much clearer. | ||
We recommend `PyCell` here, since it makes the context much clearer. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can simply remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
|
||
If `SubClass` does not provide a baseclass initialization, the compilation fails. | ||
```compile_fail | ||
# use pyo3::prelude::*; | ||
use pyo3::PyClassShell; | ||
use pyo3::PyCell; | ||
|
||
#[pyclass] | ||
struct BaseClass { | ||
|
@@ -761,16 +761,16 @@ struct GCTracked {} // Fails because it does not implement PyGCProtocol | |
Iterators can be defined using the | ||
[`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait. | ||
It includes two methods `__iter__` and `__next__`: | ||
* `fn __iter__(slf: &mut PyClassShell<Self>) -> PyResult<impl IntoPy<PyObject>>` | ||
* `fn __next__(slf: &mut PyClassShell<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>` | ||
* `fn __iter__(slf: &mut PyCell<Self>) -> PyResult<impl IntoPy<PyObject>>` | ||
* `fn __next__(slf: &mut PyCell<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>` | ||
|
||
Returning `Ok(None)` from `__next__` indicates that that there are no further items. | ||
|
||
Example: | ||
|
||
```rust | ||
use pyo3::prelude::*; | ||
use pyo3::{PyIterProtocol, PyClassShell}; | ||
use pyo3::{PyIterProtocol, PyCell}; | ||
|
||
#[pyclass] | ||
struct MyIterator { | ||
|
@@ -779,10 +779,10 @@ struct MyIterator { | |
|
||
#[pyproto] | ||
impl PyIterProtocol for MyIterator { | ||
fn __iter__(slf: &mut PyClassShell<Self>) -> PyResult<Py<MyIterator>> { | ||
fn __iter__(slf: &mut PyCell<Self>) -> PyResult<Py<MyIterator>> { | ||
Ok(slf.into()) | ||
} | ||
fn __next__(slf: &mut PyClassShell<Self>) -> PyResult<Option<PyObject>> { | ||
fn __next__(slf: &mut PyCell<Self>) -> PyResult<Option<PyObject>> { | ||
Ok(slf.iter.next()) | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,12 +29,16 @@ pub enum FnType { | |
FnCall, | ||
FnClass, | ||
FnStatic, | ||
PySelf(syn::TypeReference), | ||
// self_: &PyCell<Self>, | ||
PySelfRef(syn::TypeReference), | ||
// self_: PyRef<Self> or PyRefMut<Self> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Forgotten comments? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are comments for developers. |
||
PySelfPath(syn::TypePath), | ||
} | ||
|
||
#[derive(Clone, PartialEq, Debug)] | ||
pub struct FnSpec<'a> { | ||
pub tp: FnType, | ||
pub self_: Option<bool>, | ||
// Rust function name | ||
pub name: &'a syn::Ident, | ||
// Wrapped python name. This should not have any leading r#. | ||
|
@@ -54,6 +58,14 @@ pub fn get_return_info(output: &syn::ReturnType) -> syn::Type { | |
} | ||
|
||
impl<'a> FnSpec<'a> { | ||
/// Generate the code for borrowing self | ||
pub(crate) fn borrow_self(&self) -> TokenStream { | ||
let is_mut = self | ||
.self_ | ||
.expect("impl_borrow_self is called for non-self fn"); | ||
crate::utils::borrow_self(is_mut, true) | ||
} | ||
|
||
/// Parser function signature and function attributes | ||
pub fn parse( | ||
sig: &'a syn::Signature, | ||
|
@@ -67,19 +79,19 @@ impl<'a> FnSpec<'a> { | |
mut python_name, | ||
} = parse_method_attributes(meth_attrs, allow_custom_name)?; | ||
|
||
let mut has_self = false; | ||
let mut self_ = None; | ||
let mut arguments = Vec::new(); | ||
for input in sig.inputs.iter() { | ||
match input { | ||
syn::FnArg::Receiver(_) => { | ||
has_self = true; | ||
syn::FnArg::Receiver(recv) => { | ||
self_ = Some(recv.mutability.is_some()); | ||
} | ||
syn::FnArg::Typed(syn::PatType { | ||
ref pat, ref ty, .. | ||
}) => { | ||
// skip first argument (cls) | ||
if fn_type == FnType::FnClass && !has_self { | ||
has_self = true; | ||
if fn_type == FnType::FnClass && self_.is_none() { | ||
self_ = Some(false); | ||
continue; | ||
} | ||
|
||
|
@@ -114,18 +126,18 @@ impl<'a> FnSpec<'a> { | |
|
||
let ty = get_return_info(&sig.output); | ||
|
||
if fn_type == FnType::Fn && !has_self { | ||
if fn_type == FnType::Fn && self_.is_none() { | ||
if arguments.is_empty() { | ||
return Err(syn::Error::new_spanned( | ||
name, | ||
"Static method needs #[staticmethod] attribute", | ||
)); | ||
} | ||
let tp = match arguments.remove(0).ty { | ||
syn::Type::Reference(r) => replace_self(r)?, | ||
fn_type = match arguments.remove(0).ty { | ||
syn::Type::Reference(r) => FnType::PySelfRef(replace_self_in_ref(r)?), | ||
syn::Type::Path(p) => FnType::PySelfPath(replace_self_in_path(p)), | ||
x => return Err(syn::Error::new_spanned(x, "Invalid type as custom self")), | ||
}; | ||
fn_type = FnType::PySelf(tp); | ||
} | ||
|
||
// "Tweak" getter / setter names: strip off set_ and get_ if needed | ||
|
@@ -158,9 +170,11 @@ impl<'a> FnSpec<'a> { | |
}; | ||
|
||
let text_signature = match &fn_type { | ||
FnType::Fn | FnType::PySelf(_) | FnType::FnClass | FnType::FnStatic => { | ||
utils::parse_text_signature_attrs(&mut *meth_attrs, name)? | ||
} | ||
FnType::Fn | ||
| FnType::PySelfRef(_) | ||
| FnType::PySelfPath(_) | ||
| FnType::FnClass | ||
| FnType::FnStatic => utils::parse_text_signature_attrs(&mut *meth_attrs, name)?, | ||
FnType::FnNew => parse_erroneous_text_signature( | ||
"text_signature not allowed on __new__; if you want to add a signature on \ | ||
__new__, put it on the struct definition instead", | ||
|
@@ -174,6 +188,7 @@ impl<'a> FnSpec<'a> { | |
|
||
Ok(FnSpec { | ||
tp: fn_type, | ||
self_, | ||
name, | ||
python_name, | ||
attrs: fn_attrs, | ||
|
@@ -514,17 +529,24 @@ fn parse_method_name_attribute( | |
} | ||
|
||
// 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] }, | ||
})) | ||
} | ||
fn replace_self_in_ref(refn: &syn::TypeReference) -> syn::Result<syn::TypeReference> { | ||
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")), | ||
}; | ||
replace_self_impl(tp); | ||
res.lifetime = None; | ||
Ok(res) | ||
} | ||
|
||
fn replace_self_in_path(tp: &syn::TypePath) -> syn::TypePath { | ||
let mut res = tp.to_owned(); | ||
replace_self_impl(&mut res); | ||
res | ||
} | ||
|
||
fn replace_self_impl(tp: &mut syn::TypePath) { | ||
for seg in &mut tp.path.segments { | ||
if let syn::PathArguments::AngleBracketed(ref mut g) = seg.arguments { | ||
let mut args = syn::punctuated::Punctuated::new(); | ||
|
@@ -546,6 +568,9 @@ fn replace_self(refn: &syn::TypeReference) -> syn::Result<syn::TypeReference> { | |
g.args = args; | ||
} | ||
} | ||
res.lifetime = None; | ||
Ok(res) | ||
fn infer(span: proc_macro2::Span) -> syn::GenericArgument { | ||
syn::GenericArgument::Type(syn::Type::Infer(syn::TypeInfer { | ||
underscore_token: syn::token::Underscore { spans: [span] }, | ||
})) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,27 +54,22 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) { | |
} | ||
|
||
/// Transforms a rust fn arg parsed with syn into a method::FnArg | ||
fn wrap_fn_argument<'a>(input: &'a syn::FnArg, name: &'a Ident) -> Option<method::FnArg<'a>> { | ||
match input { | ||
syn::FnArg::Receiver(_) => None, | ||
syn::FnArg::Typed(ref cap) => { | ||
let (mutability, by_ref, ident) = match *cap.pat { | ||
syn::Pat::Ident(ref patid) => (&patid.mutability, &patid.by_ref, &patid.ident), | ||
_ => panic!("unsupported argument: {:?}", cap.pat), | ||
}; | ||
fn wrap_fn_argument<'a>(cap: &'a syn::PatType, name: &'a Ident) -> method::FnArg<'a> { | ||
let (mutability, by_ref, ident) = match *cap.pat { | ||
syn::Pat::Ident(ref patid) => (&patid.mutability, &patid.by_ref, &patid.ident), | ||
_ => panic!("unsupported argument: {:?}", cap.pat), | ||
kngwyu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
let py = crate::utils::if_type_is_python(&cap.ty); | ||
let opt = method::check_arg_ty_and_optional(&name, &cap.ty); | ||
Some(method::FnArg { | ||
name: ident, | ||
mutability, | ||
by_ref, | ||
ty: &cap.ty, | ||
optional: opt, | ||
py, | ||
reference: method::is_ref(&name, &cap.ty), | ||
}) | ||
} | ||
let py = crate::utils::if_type_is_python(&cap.ty); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bikeshed while we're here: I really wish this util function was just called
reads nicer than
|
||
let opt = method::check_arg_ty_and_optional(&name, &cap.ty); | ||
method::FnArg { | ||
name: ident, | ||
mutability, | ||
by_ref, | ||
ty: &cap.ty, | ||
optional: opt, | ||
py, | ||
reference: method::is_ref(&name, &cap.ty), | ||
} | ||
} | ||
|
||
|
@@ -138,10 +133,16 @@ pub fn add_fn_to_module( | |
pyfn_attrs: Vec<pyfunction::Argument>, | ||
) -> TokenStream { | ||
let mut arguments = Vec::new(); | ||
let mut self_ = None; | ||
|
||
for input in func.sig.inputs.iter() { | ||
if let Some(fn_arg) = wrap_fn_argument(input, &func.sig.ident) { | ||
arguments.push(fn_arg); | ||
match input { | ||
syn::FnArg::Receiver(recv) => { | ||
self_ = Some(recv.mutability.is_some()); | ||
} | ||
syn::FnArg::Typed(ref cap) => { | ||
arguments.push(wrap_fn_argument(cap, &func.sig.ident)); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -160,6 +161,7 @@ pub fn add_fn_to_module( | |
|
||
let spec = method::FnSpec { | ||
tp: method::FnType::Fn, | ||
self_, | ||
name: &function_wrapper_ident, | ||
python_name, | ||
attrs: pyfn_attrs, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it a bit confusing that only
into_super
was used in the example.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, this was left unchanged... thanks