Allow #[getter] and #[setter] functions to take PyRef

This commit is contained in:
David Hewitt 2020-06-23 15:19:20 +01:00
parent 24fb2bb1a0
commit e140b729fc
12 changed files with 321 additions and 245 deletions

View File

@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Fix passing explicit `None` to `Option<T>` argument `#[pyfunction]` with a default value. [#936](https://github.com/PyO3/pyo3/pull/936) - Fix passing explicit `None` to `Option<T>` argument `#[pyfunction]` with a default value. [#936](https://github.com/PyO3/pyo3/pull/936)
- Fix `PyClass.__new__`'s not respecting subclasses when inherited by a Python class. [#990](https://github.com/PyO3/pyo3/pull/990) - Fix `PyClass.__new__`'s not respecting subclasses when inherited by a Python class. [#990](https://github.com/PyO3/pyo3/pull/990)
- Fix returning `Option<T>` from `#[pyproto]` methods. [#996](https://github.com/PyO3/pyo3/pull/996) - Fix returning `Option<T>` from `#[pyproto]` methods. [#996](https://github.com/PyO3/pyo3/pull/996)
- Fix accepting `PyRef<Self>` and `PyRefMut<Self>` to `#[getter]` and `#[setter]` methods. [#999](https://github.com/PyO3/pyo3/pull/999)
## [0.10.1] - 2020-05-14 ## [0.10.1] - 2020-05-14
### Fixed ### Fixed

View File

@ -4,8 +4,8 @@ use crate::pyfunction::Argument;
use crate::pyfunction::{parse_name_attribute, PyFunctionAttr}; use crate::pyfunction::{parse_name_attribute, PyFunctionAttr};
use crate::utils; use crate::utils;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote;
use quote::ToTokens; use quote::ToTokens;
use quote::{quote, quote_spanned};
use syn::ext::IdentExt; use syn::ext::IdentExt;
use syn::spanned::Spanned; use syn::spanned::Spanned;
@ -20,26 +20,72 @@ pub struct FnArg<'a> {
pub reference: bool, pub reference: bool,
} }
#[derive(Clone, PartialEq, Debug, Copy, Eq)]
pub enum MethodTypeAttribute {
/// #[new]
New,
/// #[call]
Call,
/// #[classmethod]
ClassMethod,
/// #[classattr]
ClassAttribute,
/// #[staticmethod]
StaticMethod,
/// #[getter]
Getter,
/// #[setter]
Setter,
}
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum FnType { pub enum FnType {
Getter, Getter(SelfType),
Setter, Setter(SelfType),
Fn, Fn(SelfType),
FnCall(SelfType),
FnNew, FnNew,
FnCall,
FnClass, FnClass,
FnStatic, FnStatic,
ClassAttribute, ClassAttribute,
/// For methods taht have `self_: &PyCell<Self>` instead of self receiver }
PySelfRef(syn::TypeReference),
/// For methods taht have `self_: PyRef<Self>` or `PyRefMut<Self>` instead of self receiver #[derive(Clone, PartialEq, Debug)]
PySelfPath(syn::TypePath), pub enum SelfType {
Receiver { mutable: bool },
TryFromPyCell(syn::Type),
}
impl SelfType {
pub fn receiver(&self, cls: &syn::Type) -> TokenStream {
match self {
SelfType::Receiver { mutable: false } => {
quote! {
let _cell = _py.from_borrowed_ptr::<pyo3::PyCell<#cls>>(_slf);
let _ref = _cell.try_borrow()?;
let _slf = &_ref;
}
}
SelfType::Receiver { mutable: true } => {
quote! {
let _cell = _py.from_borrowed_ptr::<pyo3::PyCell<#cls>>(_slf);
let mut _ref = _cell.try_borrow_mut()?;
let _slf = &mut _ref;
}
}
SelfType::TryFromPyCell(ty) => {
quote_spanned! { ty.span() =>
let _cell = _py.from_borrowed_ptr::<pyo3::PyCell<#cls>>(_slf);
let _slf = std::convert::TryFrom::try_from(_cell)?;
}
}
}
}
} }
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct FnSpec<'a> { pub struct FnSpec<'a> {
pub tp: FnType, pub tp: FnType,
pub self_: Option<bool>,
// Rust function name // Rust function name
pub name: &'a syn::Ident, pub name: &'a syn::Ident,
// Wrapped python name. This should not have any leading r#. // Wrapped python name. This should not have any leading r#.
@ -58,15 +104,18 @@ pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
} }
} }
impl<'a> FnSpec<'a> { pub fn parse_method_receiver(arg: &syn::FnArg) -> syn::Result<SelfType> {
/// Generate the code for borrowing self match arg {
pub(crate) fn borrow_self(&self) -> TokenStream { syn::FnArg::Receiver(recv) => Ok(SelfType::Receiver {
let is_mut = self mutable: recv.mutability.is_some(),
.self_ }),
.expect("impl_borrow_self is called for non-self fn"); syn::FnArg::Typed(syn::PatType { ref ty, .. }) => {
crate::utils::borrow_self(is_mut) Ok(SelfType::TryFromPyCell(ty.as_ref().clone()))
}
} }
}
impl<'a> FnSpec<'a> {
/// Parser function signature and function attributes /// Parser function signature and function attributes
pub fn parse( pub fn parse(
sig: &'a syn::Signature, sig: &'a syn::Signature,
@ -75,27 +124,107 @@ impl<'a> FnSpec<'a> {
) -> syn::Result<FnSpec<'a>> { ) -> syn::Result<FnSpec<'a>> {
let name = &sig.ident; let name = &sig.ident;
let MethodAttributes { let MethodAttributes {
ty: mut fn_type, ty: fn_type_attr,
args: fn_attrs, args: fn_attrs,
mut python_name, mut python_name,
} = parse_method_attributes(meth_attrs, allow_custom_name)?; } = parse_method_attributes(meth_attrs, allow_custom_name)?;
let mut self_ = None;
let mut arguments = Vec::new(); let mut arguments = Vec::new();
for input in sig.inputs.iter() { let mut inputs_iter = sig.inputs.iter();
// Parse receiver & function type for various method types
let fn_type = match fn_type_attr {
Some(MethodTypeAttribute::StaticMethod) => FnType::FnStatic,
Some(MethodTypeAttribute::ClassAttribute) => {
if !sig.inputs.is_empty() {
return Err(syn::Error::new_spanned(
name,
"Class attribute methods cannot take arguments",
));
}
FnType::ClassAttribute
}
Some(MethodTypeAttribute::New) => FnType::FnNew,
Some(MethodTypeAttribute::ClassMethod) => {
// Skip first argument for classmethod - always &PyType
let _ = inputs_iter.next();
FnType::FnClass
}
Some(MethodTypeAttribute::Call) => FnType::FnCall(
inputs_iter
.next()
.ok_or_else(|| syn::Error::new_spanned(sig, "expected receiver for #[call]"))
.and_then(parse_method_receiver)?,
),
Some(MethodTypeAttribute::Getter) => {
// Strip off "get_" prefix if needed
if python_name.is_none() {
const PREFIX: &str = "get_";
let ident = sig.ident.unraw().to_string();
if ident.starts_with(PREFIX) {
python_name = Some(syn::Ident::new(&ident[PREFIX.len()..], ident.span()))
}
}
FnType::Getter(
inputs_iter
.next()
.ok_or_else(|| {
syn::Error::new_spanned(sig, "expected receiver for #[getter]")
})
.and_then(parse_method_receiver)?,
)
}
Some(MethodTypeAttribute::Setter) => {
if python_name.is_none() {
const PREFIX: &str = "set_";
let ident = sig.ident.unraw().to_string();
if ident.starts_with(PREFIX) {
python_name = Some(syn::Ident::new(&ident[PREFIX.len()..], ident.span()))
}
}
FnType::Setter(
inputs_iter
.next()
.ok_or_else(|| {
syn::Error::new_spanned(sig, "expected receiver for #[setter]")
})
.and_then(parse_method_receiver)?,
)
}
None => {
FnType::Fn(
inputs_iter
.next()
.ok_or_else(
// No arguments - might be a static method?
|| {
syn::Error::new_spanned(
sig,
"Static method needs #[staticmethod] attribute",
)
},
)
.and_then(parse_method_receiver)?,
)
}
};
// parse rest of arguments
for input in inputs_iter {
match input { match input {
syn::FnArg::Receiver(recv) => { syn::FnArg::Receiver(recv) => {
self_ = Some(recv.mutability.is_some()); return Err(syn::Error::new_spanned(
recv,
"Unexpected receiver for method",
));
} }
syn::FnArg::Typed(syn::PatType { syn::FnArg::Typed(syn::PatType {
ref pat, ref ty, .. ref pat, ref ty, ..
}) => { }) => {
// skip first argument (cls)
if fn_type == FnType::FnClass && self_.is_none() {
self_ = Some(false);
continue;
}
let (ident, by_ref, mutability) = match **pat { let (ident, by_ref, mutability) = match **pat {
syn::Pat::Ident(syn::PatIdent { syn::Pat::Ident(syn::PatIdent {
ref ident, ref ident,
@ -125,46 +254,6 @@ impl<'a> FnSpec<'a> {
} }
let ty = get_return_info(&sig.output); let ty = get_return_info(&sig.output);
if fn_type == FnType::Fn && self_.is_none() {
if arguments.is_empty() {
return Err(syn::Error::new_spanned(
name,
"Static method needs #[staticmethod] attribute",
));
}
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")),
};
}
if let FnType::ClassAttribute = &fn_type {
if self_.is_some() || !arguments.is_empty() {
return Err(syn::Error::new_spanned(
name,
"Class attribute methods cannot take arguments",
));
}
}
// "Tweak" getter / setter names: strip off set_ and get_ if needed
if let FnType::Getter | FnType::Setter = &fn_type {
if python_name.is_none() {
let prefix = match &fn_type {
FnType::Getter => "get_",
FnType::Setter => "set_",
_ => unreachable!(),
};
let ident = sig.ident.unraw().to_string();
if ident.starts_with(prefix) {
python_name = Some(syn::Ident::new(&ident[prefix.len()..], ident.span()))
}
}
}
let python_name = python_name.unwrap_or_else(|| name.unraw()); let python_name = python_name.unwrap_or_else(|| name.unraw());
let mut parse_erroneous_text_signature = |error_msg: &str| { let mut parse_erroneous_text_signature = |error_msg: &str| {
@ -179,16 +268,14 @@ impl<'a> FnSpec<'a> {
}; };
let text_signature = match &fn_type { let text_signature = match &fn_type {
FnType::Fn FnType::Fn(_) | FnType::FnClass | FnType::FnStatic => {
| FnType::PySelfRef(_) utils::parse_text_signature_attrs(&mut *meth_attrs, name)?
| FnType::PySelfPath(_) }
| FnType::FnClass
| FnType::FnStatic => utils::parse_text_signature_attrs(&mut *meth_attrs, name)?,
FnType::FnNew => parse_erroneous_text_signature( FnType::FnNew => parse_erroneous_text_signature(
"text_signature not allowed on __new__; if you want to add a signature on \ "text_signature not allowed on __new__; if you want to add a signature on \
__new__, put it on the struct definition instead", __new__, put it on the struct definition instead",
)?, )?,
FnType::FnCall | FnType::Getter | FnType::Setter | FnType::ClassAttribute => { FnType::FnCall(_) | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => {
parse_erroneous_text_signature("text_signature not allowed with this attribute")? parse_erroneous_text_signature("text_signature not allowed with this attribute")?
} }
}; };
@ -197,7 +284,6 @@ impl<'a> FnSpec<'a> {
Ok(FnSpec { Ok(FnSpec {
tp: fn_type, tp: fn_type,
self_,
name, name,
python_name, python_name,
attrs: fn_attrs, attrs: fn_attrs,
@ -311,7 +397,7 @@ pub(crate) fn check_ty_optional(ty: &syn::Type) -> Option<&syn::Type> {
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
struct MethodAttributes { struct MethodAttributes {
ty: FnType, ty: Option<MethodTypeAttribute>,
args: Vec<Argument>, args: Vec<Argument>,
python_name: Option<syn::Ident>, python_name: Option<syn::Ident>,
} }
@ -322,27 +408,40 @@ fn parse_method_attributes(
) -> syn::Result<MethodAttributes> { ) -> syn::Result<MethodAttributes> {
let mut new_attrs = Vec::new(); let mut new_attrs = Vec::new();
let mut args = Vec::new(); let mut args = Vec::new();
let mut res: Option<FnType> = None; let mut ty: Option<MethodTypeAttribute> = None;
let mut property_name = None; let mut property_name = None;
macro_rules! set_ty {
($new_ty:expr, $ident:expr) => {
if ty.is_some() {
return Err(syn::Error::new_spanned(
$ident,
"Cannot specify a second method type",
));
} else {
ty = Some($new_ty);
}
};
}
for attr in attrs.iter() { for attr in attrs.iter() {
match attr.parse_meta()? { match attr.parse_meta()? {
syn::Meta::Path(ref name) => { syn::Meta::Path(ref name) => {
if name.is_ident("new") || name.is_ident("__new__") { if name.is_ident("new") || name.is_ident("__new__") {
res = Some(FnType::FnNew) set_ty!(MethodTypeAttribute::New, name);
} else if name.is_ident("init") || name.is_ident("__init__") { } else if name.is_ident("init") || name.is_ident("__init__") {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
name, name,
"#[init] is disabled since PyO3 0.9.0", "#[init] is disabled since PyO3 0.9.0",
)); ));
} else if name.is_ident("call") || name.is_ident("__call__") { } else if name.is_ident("call") || name.is_ident("__call__") {
res = Some(FnType::FnCall) set_ty!(MethodTypeAttribute::Call, name);
} else if name.is_ident("classmethod") { } else if name.is_ident("classmethod") {
res = Some(FnType::FnClass) set_ty!(MethodTypeAttribute::ClassMethod, name);
} else if name.is_ident("staticmethod") { } else if name.is_ident("staticmethod") {
res = Some(FnType::FnStatic) set_ty!(MethodTypeAttribute::StaticMethod, name);
} else if name.is_ident("classattr") { } else if name.is_ident("classattr") {
res = Some(FnType::ClassAttribute) set_ty!(MethodTypeAttribute::ClassAttribute, name);
} else if name.is_ident("setter") || name.is_ident("getter") { } else if name.is_ident("setter") || name.is_ident("getter") {
if let syn::AttrStyle::Inner(_) = attr.style { if let syn::AttrStyle::Inner(_) = attr.style {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
@ -350,16 +449,10 @@ fn parse_method_attributes(
"Inner style attribute is not supported for setter and getter", "Inner style attribute is not supported for setter and getter",
)); ));
} }
if res != None {
return Err(syn::Error::new_spanned(
attr,
"setter/getter attribute can not be used mutiple times",
));
}
if name.is_ident("setter") { if name.is_ident("setter") {
res = Some(FnType::Setter) set_ty!(MethodTypeAttribute::Setter, name);
} else { } else {
res = Some(FnType::Getter) set_ty!(MethodTypeAttribute::Getter, name);
} }
} else { } else {
new_attrs.push(attr.clone()) new_attrs.push(attr.clone())
@ -371,14 +464,14 @@ fn parse_method_attributes(
.. ..
}) => { }) => {
if path.is_ident("new") { if path.is_ident("new") {
res = Some(FnType::FnNew) set_ty!(MethodTypeAttribute::New, path);
} else if path.is_ident("init") { } else if path.is_ident("init") {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
path, path,
"#[init] is disabled since PyO3 0.9.0", "#[init] is disabled since PyO3 0.9.0",
)); ));
} else if path.is_ident("call") { } else if path.is_ident("call") {
res = Some(FnType::FnCall) set_ty!(MethodTypeAttribute::Call, path);
} else if path.is_ident("setter") || path.is_ident("getter") { } else if path.is_ident("setter") || path.is_ident("getter") {
if let syn::AttrStyle::Inner(_) = attr.style { if let syn::AttrStyle::Inner(_) = attr.style {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
@ -386,12 +479,6 @@ fn parse_method_attributes(
"Inner style attribute is not supported for setter and getter", "Inner style attribute is not supported for setter and getter",
)); ));
} }
if res != None {
return Err(syn::Error::new_spanned(
attr,
"setter/getter attribute can not be used mutiple times",
));
}
if nested.len() != 1 { if nested.len() != 1 {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
attr, attr,
@ -399,10 +486,10 @@ fn parse_method_attributes(
)); ));
} }
res = if path.is_ident("setter") { if path.is_ident("setter") {
Some(FnType::Setter) set_ty!(MethodTypeAttribute::Setter, path);
} else { } else {
Some(FnType::Getter) set_ty!(MethodTypeAttribute::Getter, path);
}; };
property_name = match nested.first().unwrap() { property_name = match nested.first().unwrap() {
@ -439,9 +526,8 @@ fn parse_method_attributes(
attrs.clear(); attrs.clear();
attrs.extend(new_attrs); attrs.extend(new_attrs);
let ty = res.unwrap_or(FnType::Fn);
let python_name = if allow_custom_name { let python_name = if allow_custom_name {
parse_method_name_attribute(&ty, attrs, property_name)? parse_method_name_attribute(ty.as_ref(), attrs, property_name)?
} else { } else {
property_name property_name
}; };
@ -454,77 +540,33 @@ fn parse_method_attributes(
} }
fn parse_method_name_attribute( fn parse_method_name_attribute(
ty: &FnType, ty: Option<&MethodTypeAttribute>,
attrs: &mut Vec<syn::Attribute>, attrs: &mut Vec<syn::Attribute>,
property_name: Option<syn::Ident>, property_name: Option<syn::Ident>,
) -> syn::Result<Option<syn::Ident>> { ) -> syn::Result<Option<syn::Ident>> {
use MethodTypeAttribute::*;
let name = parse_name_attribute(attrs)?; let name = parse_name_attribute(attrs)?;
// Reject some invalid combinations // Reject some invalid combinations
if let Some(name) = &name { if let Some(name) = &name {
match ty { if let Some(ty) = ty {
FnType::FnNew | FnType::FnCall | FnType::Getter | FnType::Setter => { match ty {
return Err(syn::Error::new_spanned( New | Call | Getter | Setter => {
name, return Err(syn::Error::new_spanned(
"name not allowed with this attribute", name,
)) "name not allowed with this method type",
))
}
_ => {}
} }
_ => {}
} }
} }
// Thanks to check above we can be sure that this generates the right python name // Thanks to check above we can be sure that this generates the right python name
Ok(match ty { Ok(match ty {
FnType::FnNew => Some(syn::Ident::new("__new__", proc_macro2::Span::call_site())), Some(New) => Some(syn::Ident::new("__new__", proc_macro2::Span::call_site())),
FnType::FnCall => Some(syn::Ident::new("__call__", proc_macro2::Span::call_site())), Some(Call) => Some(syn::Ident::new("__call__", proc_macro2::Span::call_site())),
FnType::Getter | FnType::Setter => property_name, Some(Getter) | Some(Setter) => property_name,
_ => name, _ => name,
}) })
} }
// Replace &A<Self> with &A<_>
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();
for arg in &g.args {
let mut add_arg = true;
if let syn::GenericArgument::Lifetime(_) = arg {
add_arg = false;
}
if let syn::GenericArgument::Type(syn::Type::Path(p)) = arg {
if p.path.segments.len() == 1 && p.path.segments[0].ident == "Self" {
args.push(infer(p.span()));
add_arg = false;
}
}
if add_arg {
args.push(arg.clone());
}
}
g.args = args;
}
}
fn infer(span: proc_macro2::Span) -> syn::GenericArgument {
syn::GenericArgument::Type(syn::Type::Infer(syn::TypeInfer {
underscore_token: syn::token::Underscore { spans: [span] },
}))
}
}

View File

@ -140,12 +140,14 @@ pub fn add_fn_to_module(
pyfn_attrs: Vec<pyfunction::Argument>, pyfn_attrs: Vec<pyfunction::Argument>,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let mut arguments = Vec::new(); let mut arguments = Vec::new();
let mut self_ = None;
for input in func.sig.inputs.iter() { for input in func.sig.inputs.iter() {
match input { match input {
syn::FnArg::Receiver(recv) => { syn::FnArg::Receiver(_) => {
self_ = Some(recv.mutability.is_some()); return Err(syn::Error::new_spanned(
input,
"Unexpected receiver for #[pyfn]",
))
} }
syn::FnArg::Typed(ref cap) => { syn::FnArg::Typed(ref cap) => {
arguments.push(wrap_fn_argument(cap, &func.sig.ident)?); arguments.push(wrap_fn_argument(cap, &func.sig.ident)?);
@ -161,8 +163,7 @@ pub fn add_fn_to_module(
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident); let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
let spec = method::FnSpec { let spec = method::FnSpec {
tp: method::FnType::Fn, tp: method::FnType::FnStatic,
self_,
name: &function_wrapper_ident, name: &function_wrapper_ident,
python_name, python_name,
attrs: pyfn_attrs, attrs: pyfn_attrs,

View File

@ -1,6 +1,6 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::method::FnType; use crate::method::{FnType, SelfType};
use crate::pymethod::{ use crate::pymethod::{
impl_py_getter_def, impl_py_setter_def, impl_wrap_getter, impl_wrap_setter, PropertyType, impl_py_getter_def, impl_py_setter_def, impl_wrap_getter, impl_wrap_setter, PropertyType,
}; };
@ -185,9 +185,9 @@ fn parse_descriptors(item: &mut syn::Field) -> syn::Result<Vec<FnType>> {
for meta in list.nested.iter() { for meta in list.nested.iter() {
if let syn::NestedMeta::Meta(ref metaitem) = meta { if let syn::NestedMeta::Meta(ref metaitem) = meta {
if metaitem.path().is_ident("get") { if metaitem.path().is_ident("get") {
descs.push(FnType::Getter); descs.push(FnType::Getter(SelfType::Receiver { mutable: false }));
} else if metaitem.path().is_ident("set") { } else if metaitem.path().is_ident("set") {
descs.push(FnType::Setter); descs.push(FnType::Setter(SelfType::Receiver { mutable: true }));
} else { } else {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
metaitem, metaitem,
@ -450,16 +450,16 @@ fn impl_descriptors(
let doc = utils::get_doc(&field.attrs, None, true) let doc = utils::get_doc(&field.attrs, None, true)
.unwrap_or_else(|_| syn::LitStr::new(&name.to_string(), name.span())); .unwrap_or_else(|_| syn::LitStr::new(&name.to_string(), name.span()));
match *desc { match desc {
FnType::Getter => Ok(impl_py_getter_def( FnType::Getter(self_ty) => Ok(impl_py_getter_def(
&name, &name,
&doc, &doc,
&impl_wrap_getter(&cls, PropertyType::Descriptor(&field))?, &impl_wrap_getter(&cls, PropertyType::Descriptor(&field), &self_ty)?,
)), )),
FnType::Setter => Ok(impl_py_setter_def( FnType::Setter(self_ty) => Ok(impl_py_setter_def(
&name, &name,
&doc, &doc,
&impl_wrap_setter(&cls, PropertyType::Descriptor(&field))?, &impl_wrap_setter(&cls, PropertyType::Descriptor(&field), &self_ty)?,
)), )),
_ => unreachable!(), _ => unreachable!(),
} }

View File

@ -1,6 +1,6 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::konst::ConstSpec; use crate::konst::ConstSpec;
use crate::method::{FnArg, FnSpec, FnType}; use crate::method::{FnArg, FnSpec, FnType, SelfType};
use crate::utils; use crate::utils;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
@ -19,30 +19,26 @@ pub fn gen_py_method(
check_generic(sig)?; check_generic(sig)?;
let spec = FnSpec::parse(sig, &mut *meth_attrs, true)?; let spec = FnSpec::parse(sig, &mut *meth_attrs, true)?;
Ok(match spec.tp { Ok(match &spec.tp {
FnType::Fn => impl_py_method_def(&spec, &impl_wrap(cls, &spec, true)), FnType::Fn(self_ty) => impl_py_method_def(&spec, &impl_wrap(cls, &spec, self_ty, true)),
FnType::PySelfRef(ref self_ty) => {
impl_py_method_def(&spec, &impl_wrap_pyslf(cls, &spec, self_ty, true))
}
FnType::PySelfPath(ref self_ty) => {
impl_py_method_def(&spec, &impl_wrap_pyslf(cls, &spec, self_ty, true))
}
FnType::FnNew => impl_py_method_def_new(&spec, &impl_wrap_new(cls, &spec)), FnType::FnNew => impl_py_method_def_new(&spec, &impl_wrap_new(cls, &spec)),
FnType::FnCall => impl_py_method_def_call(&spec, &impl_wrap(cls, &spec, false)), FnType::FnCall(self_ty) => {
impl_py_method_def_call(&spec, &impl_wrap(cls, &spec, self_ty, false))
}
FnType::FnClass => impl_py_method_def_class(&spec, &impl_wrap_class(cls, &spec)), FnType::FnClass => impl_py_method_def_class(&spec, &impl_wrap_class(cls, &spec)),
FnType::FnStatic => impl_py_method_def_static(&spec, &impl_wrap_static(cls, &spec)), FnType::FnStatic => impl_py_method_def_static(&spec, &impl_wrap_static(cls, &spec)),
FnType::ClassAttribute => { FnType::ClassAttribute => {
impl_py_method_class_attribute(&spec, &impl_wrap_class_attribute(cls, &spec)) impl_py_method_class_attribute(&spec, &impl_wrap_class_attribute(cls, &spec))
} }
FnType::Getter => impl_py_getter_def( FnType::Getter(self_ty) => impl_py_getter_def(
&spec.python_name, &spec.python_name,
&spec.doc, &spec.doc,
&impl_wrap_getter(cls, PropertyType::Function(&spec))?, &impl_wrap_getter(cls, PropertyType::Function(&spec), self_ty)?,
), ),
FnType::Setter => impl_py_setter_def( FnType::Setter(self_ty) => impl_py_setter_def(
&spec.python_name, &spec.python_name,
&spec.doc, &spec.doc,
&impl_wrap_setter(cls, PropertyType::Function(&spec))?, &impl_wrap_setter(cls, PropertyType::Function(&spec), self_ty)?,
), ),
}) })
} }
@ -81,31 +77,14 @@ pub fn gen_py_const(
} }
/// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords) /// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap(cls: &syn::Type, spec: &FnSpec<'_>, noargs: bool) -> TokenStream { pub fn impl_wrap(
let body = impl_call(cls, &spec);
let borrow_self = spec.borrow_self();
let slf = quote! {
let _slf = _py.from_borrowed_ptr::<pyo3::PyCell<#cls>>(_slf);
#borrow_self
};
impl_wrap_common(cls, spec, noargs, slf, body)
}
pub fn impl_wrap_pyslf(
cls: &syn::Type, cls: &syn::Type,
spec: &FnSpec<'_>, spec: &FnSpec<'_>,
self_ty: impl quote::ToTokens, self_ty: &SelfType,
noargs: bool, noargs: bool,
) -> TokenStream { ) -> TokenStream {
let names = get_arg_names(spec); let body = impl_call(cls, &spec);
let name = &spec.name; let slf = self_ty.receiver(cls);
let body = quote! {
#cls::#name(_slf, #(#names),*)
};
let slf = quote! {
let _cell = _py.from_borrowed_ptr::<pyo3::PyCell<#cls>>(_slf);
let _slf: #self_ty = std::convert::TryFrom::try_from(_cell)?;
};
impl_wrap_common(cls, spec, noargs, slf, body) impl_wrap_common(cls, spec, noargs, slf, body)
} }
@ -156,11 +135,11 @@ fn impl_wrap_common(
} }
/// Generate function wrapper for protocol method (PyCFunction, PyCFunctionWithKeywords) /// Generate function wrapper for protocol method (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_proto_wrap(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { pub fn impl_proto_wrap(cls: &syn::Type, spec: &FnSpec<'_>, self_ty: &SelfType) -> TokenStream {
let python_name = &spec.python_name; let python_name = &spec.python_name;
let cb = impl_call(cls, &spec); let cb = impl_call(cls, &spec);
let body = impl_arg_params(&spec, cb); let body = impl_arg_params(&spec, cb);
let borrow_self = spec.borrow_self(); let slf = self_ty.receiver(cls);
quote! { quote! {
#[allow(unused_mut)] #[allow(unused_mut)]
@ -171,8 +150,7 @@ pub fn impl_proto_wrap(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
{ {
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()");
pyo3::callback_body_without_convert!(_py, { pyo3::callback_body_without_convert!(_py, {
let _slf = _py.from_borrowed_ptr::<pyo3::PyCell<#cls>>(_slf); #slf
#borrow_self
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args); let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);
@ -282,7 +260,7 @@ pub fn impl_wrap_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStr
} }
} }
fn impl_call_getter(spec: &FnSpec) -> syn::Result<TokenStream> { fn impl_call_getter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream> {
let (py_arg, args) = split_off_python_arg(&spec.args); let (py_arg, args) = split_off_python_arg(&spec.args);
if !args.is_empty() { if !args.is_empty() {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
@ -293,10 +271,11 @@ fn impl_call_getter(spec: &FnSpec) -> syn::Result<TokenStream> {
let name = &spec.name; let name = &spec.name;
let fncall = if py_arg.is_some() { let fncall = if py_arg.is_some() {
quote! { _slf.#name(_py) } quote!(#cls::#name(_slf, _py))
} else { } else {
quote! { _slf.#name() } quote!(#cls::#name(_slf))
}; };
Ok(fncall) Ok(fncall)
} }
@ -304,6 +283,7 @@ fn impl_call_getter(spec: &FnSpec) -> syn::Result<TokenStream> {
pub(crate) fn impl_wrap_getter( pub(crate) fn impl_wrap_getter(
cls: &syn::Type, cls: &syn::Type,
property_type: PropertyType, property_type: PropertyType,
self_ty: &SelfType,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let (python_name, getter_impl) = match property_type { let (python_name, getter_impl) = match property_type {
PropertyType::Descriptor(field) => { PropertyType::Descriptor(field) => {
@ -315,25 +295,24 @@ pub(crate) fn impl_wrap_getter(
}), }),
) )
} }
PropertyType::Function(spec) => (spec.python_name.clone(), impl_call_getter(&spec)?), PropertyType::Function(spec) => (spec.python_name.clone(), impl_call_getter(cls, spec)?),
}; };
let borrow_self = crate::utils::borrow_self(false); let slf = self_ty.receiver(cls);
Ok(quote! { Ok(quote! {
unsafe extern "C" fn __wrap( unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject, _: *mut ::std::os::raw::c_void) -> *mut pyo3::ffi::PyObject _slf: *mut pyo3::ffi::PyObject, _: *mut ::std::os::raw::c_void) -> *mut pyo3::ffi::PyObject
{ {
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()");
pyo3::callback_body_without_convert!(_py, { pyo3::callback_body_without_convert!(_py, {
let _slf = _py.from_borrowed_ptr::<pyo3::PyCell<#cls>>(_slf); #slf
#borrow_self
pyo3::callback::convert(_py, #getter_impl) pyo3::callback::convert(_py, #getter_impl)
}) })
} }
}) })
} }
fn impl_call_setter(spec: &FnSpec) -> syn::Result<TokenStream> { fn impl_call_setter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream> {
let (py_arg, args) = split_off_python_arg(&spec.args); let (py_arg, args) = split_off_python_arg(&spec.args);
if args.is_empty() { if args.is_empty() {
@ -350,9 +329,9 @@ fn impl_call_setter(spec: &FnSpec) -> syn::Result<TokenStream> {
let name = &spec.name; let name = &spec.name;
let fncall = if py_arg.is_some() { let fncall = if py_arg.is_some() {
quote!(_slf.#name(_py, _val)) quote!(#cls::#name(_slf, _py, _val))
} else { } else {
quote!(_slf.#name(_val)) quote!(#cls::#name(_slf, _val))
}; };
Ok(fncall) Ok(fncall)
@ -362,16 +341,17 @@ fn impl_call_setter(spec: &FnSpec) -> syn::Result<TokenStream> {
pub(crate) fn impl_wrap_setter( pub(crate) fn impl_wrap_setter(
cls: &syn::Type, cls: &syn::Type,
property_type: PropertyType, property_type: PropertyType,
self_ty: &SelfType,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let (python_name, setter_impl) = match property_type { let (python_name, setter_impl) = match property_type {
PropertyType::Descriptor(field) => { PropertyType::Descriptor(field) => {
let name = field.ident.as_ref().unwrap(); let name = field.ident.as_ref().unwrap();
(name.unraw(), quote!({ _slf.#name = _val; })) (name.unraw(), quote!({ _slf.#name = _val; }))
} }
PropertyType::Function(spec) => (spec.python_name.clone(), impl_call_setter(&spec)?), PropertyType::Function(spec) => (spec.python_name.clone(), impl_call_setter(cls, spec)?),
}; };
let borrow_self = crate::utils::borrow_self(true); let slf = self_ty.receiver(cls);
Ok(quote! { Ok(quote! {
#[allow(unused_mut)] #[allow(unused_mut)]
unsafe extern "C" fn __wrap( unsafe extern "C" fn __wrap(
@ -380,8 +360,7 @@ pub(crate) fn impl_wrap_setter(
{ {
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()");
pyo3::callback_body_without_convert!(_py, { pyo3::callback_body_without_convert!(_py, {
let _slf = _py.from_borrowed_ptr::<pyo3::PyCell<#cls>>(_slf); #slf
#borrow_self
let _value = _py.from_borrowed_ptr::<pyo3::types::PyAny>(_value); let _value = _py.from_borrowed_ptr::<pyo3::types::PyAny>(_value);
let _val = pyo3::FromPyObject::extract(_value)?; let _val = pyo3::FromPyObject::extract(_value)?;
@ -398,10 +377,10 @@ pub fn get_arg_names(spec: &FnSpec) -> Vec<syn::Ident> {
.collect() .collect()
} }
fn impl_call(_cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { fn impl_call(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
let fname = &spec.name; let fname = &spec.name;
let names = get_arg_names(spec); let names = get_arg_names(spec);
quote! { _slf.#fname(#(#names),*) } quote! { #cls::#fname(_slf, #(#names),*) }
} }
pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream { pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {

View File

@ -2,7 +2,7 @@
use crate::defs; use crate::defs;
use crate::func::impl_method_proto; use crate::func::impl_method_proto;
use crate::method::FnSpec; use crate::method::{FnSpec, FnType};
use crate::pymethod; use crate::pymethod;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
@ -75,7 +75,16 @@ fn impl_proto_impl(
if let Some(m) = proto.get_method(&met.sig.ident) { if let Some(m) = proto.get_method(&met.sig.ident) {
let name = &met.sig.ident; let name = &met.sig.ident;
let fn_spec = FnSpec::parse(&met.sig, &mut met.attrs, false)?; let fn_spec = FnSpec::parse(&met.sig, &mut met.attrs, false)?;
let method = pymethod::impl_proto_wrap(ty, &fn_spec);
let method = if let FnType::Fn(self_ty) = &fn_spec.tp {
pymethod::impl_proto_wrap(ty, &fn_spec, &self_ty)
} else {
return Err(syn::Error::new_spanned(
&met.sig,
"Expected method with receiver for #[pyproto] method",
));
};
let coexist = if m.can_coexist { let coexist = if m.can_coexist {
// We need METH_COEXIST here to prevent __add__ from overriding __radd__ // We need METH_COEXIST here to prevent __add__ from overriding __radd__
quote!(pyo3::ffi::METH_COEXIST) quote!(pyo3::ffi::METH_COEXIST)

View File

@ -1,21 +1,8 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use proc_macro2::Span; use proc_macro2::Span;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote;
use std::fmt::Display; use std::fmt::Display;
pub(crate) fn borrow_self(is_mut: bool) -> TokenStream {
if is_mut {
quote! {
let mut _slf = _slf.try_borrow_mut()?;
}
} else {
quote! {
let _slf = _slf.try_borrow()?;
}
}
}
pub fn print_err(msg: String, t: TokenStream) { pub fn print_err(msg: String, t: TokenStream) {
println!("Error: {} in '{}'", msg, t.to_string()); println!("Error: {} in '{}'", msg, t.to_string());
} }

View File

@ -8,6 +8,8 @@ fn test_compile_errors() {
t.compile_fail("tests/ui/missing_clone.rs"); t.compile_fail("tests/ui/missing_clone.rs");
t.compile_fail("tests/ui/reject_generics.rs"); t.compile_fail("tests/ui/reject_generics.rs");
t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs"); t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs");
t.compile_fail("tests/ui/invalid_pymethod_names.rs");
t.compile_fail("tests/ui/invalid_pymethod_receiver.rs");
skip_min_stable(&t); skip_min_stable(&t);

View File

@ -106,3 +106,33 @@ fn getter_setter_autogen() {
"assert inst.text == 'Hello'; inst.text = 'There'; assert inst.text == 'There'" "assert inst.text == 'Hello'; inst.text = 'There'; assert inst.text == 'There'"
); );
} }
#[pyclass]
struct RefGetterSetter {
num: i32,
}
#[pymethods]
impl RefGetterSetter {
#[getter]
fn get_num(slf: PyRef<Self>) -> i32 {
slf.num
}
#[setter]
fn set_num(mut slf: PyRefMut<Self>, value: i32) {
slf.num = value;
}
}
#[test]
fn ref_getter_setter() {
// Regression test for #837
let gil = Python::acquire_gil();
let py = gil.python();
let inst = Py::new(py, RefGetterSetter { num: 10 }).unwrap();
py_run!(py, inst, "assert inst.num == 10");
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
}

View File

@ -1,4 +1,4 @@
error: name not allowed with this attribute error: name not allowed with this method type
--> $DIR/invalid_pymethod_names.rs:10:5 --> $DIR/invalid_pymethod_names.rs:10:5
| |
10 | #[name = "num"] 10 | #[name = "num"]
@ -10,7 +10,7 @@ error: #[name] can not be specified multiple times
17 | #[name = "foo"] 17 | #[name = "foo"]
| ^ | ^
error: name not allowed with this attribute error: name not allowed with this method type
--> $DIR/invalid_pymethod_names.rs:24:5 --> $DIR/invalid_pymethod_names.rs:24:5
| |
24 | #[name = "makenew"] 24 | #[name = "makenew"]

View File

@ -0,0 +1,11 @@
use pyo3::prelude::*;
#[pyclass]
struct MyClass {}
#[pymethods]
impl MyClass {
fn method_with_invalid_self_type(slf: i32, py: Python, index: u32) {}
}
fn main() {}

View File

@ -0,0 +1,14 @@
error[E0277]: the trait bound `i32: std::convert::From<&pyo3::pycell::PyCell<MyClass>>` is not satisfied
--> $DIR/invalid_pymethod_receiver.rs:8:43
|
8 | fn method_with_invalid_self_type(slf: i32, py: Python, index: u32) {}
| ^^^ the trait `std::convert::From<&pyo3::pycell::PyCell<MyClass>>` is not implemented for `i32`
|
= help: the following implementations were found:
<i32 as std::convert::From<bool>>
<i32 as std::convert::From<i16>>
<i32 as std::convert::From<i8>>
<i32 as std::convert::From<std::num::NonZeroI32>>
and 2 others
= note: required because of the requirements on the impl of `std::convert::Into<i32>` for `&pyo3::pycell::PyCell<MyClass>`
= note: required because of the requirements on the impl of `std::convert::TryFrom<&pyo3::pycell::PyCell<MyClass>>` for `i32`