From 4b18830f1ebff75c82a2d5599a167a7b34278eca Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Tue, 17 Dec 2019 22:14:28 +0000 Subject: [PATCH] Add `#[name]` attribute support for `#[pyfunction]` --- CHANGELOG.md | 2 +- pyo3-derive-backend/src/lib.rs | 2 +- pyo3-derive-backend/src/method.rs | 169 ++++++++++++------------- pyo3-derive-backend/src/module.rs | 4 +- pyo3-derive-backend/src/pyclass.rs | 5 +- pyo3-derive-backend/src/pyfunction.rs | 45 +++++++ pyo3-derive-backend/src/pymethod.rs | 60 ++++----- pyo3cls/src/lib.rs | 8 +- tests/test_class_basics.rs | 17 +++ tests/test_module.rs | 26 ++++ tests/ui/invalid_pymethod_names.stderr | 26 +--- 11 files changed, 215 insertions(+), 149 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec827fa5..faced376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased -* Support for `#[name = "foo"]` attribute in `#[pymethods]`. [#692](https://github.com/PyO3/pyo3/pull/692) +* Support for `#[name = "foo"]` attribute for `#[pyfunction]` and in `#[pymethods]`. [#692](https://github.com/PyO3/pyo3/pull/692) ## [0.8.4] diff --git a/pyo3-derive-backend/src/lib.rs b/pyo3-derive-backend/src/lib.rs index 2ca6b87a..9571a52c 100644 --- a/pyo3-derive-backend/src/lib.rs +++ b/pyo3-derive-backend/src/lib.rs @@ -16,7 +16,7 @@ mod utils; pub use module::{add_fn_to_module, process_functions_in_module, py_init}; pub use pyclass::{build_py_class, PyClassArgs}; -pub use pyfunction::PyFunctionAttr; +pub use pyfunction::{build_py_function, PyFunctionAttr}; pub use pyimpl::{build_py_methods, impl_methods}; pub use pyproto::build_py_proto; pub use utils::get_doc; diff --git a/pyo3-derive-backend/src/method.rs b/pyo3-derive-backend/src/method.rs index 44ba9c46..15bf5ea0 100644 --- a/pyo3-derive-backend/src/method.rs +++ b/pyo3-derive-backend/src/method.rs @@ -1,11 +1,12 @@ // Copyright (c) 2017-present PyO3 Project and Contributors use crate::pyfunction::Argument; -use crate::pyfunction::PyFunctionAttr; +use crate::pyfunction::{PyFunctionAttr, parse_name_attribute}; use crate::utils; use proc_macro2::TokenStream; use quote::quote; use quote::ToTokens; +use syn::ext::IdentExt; use syn::spanned::Spanned; #[derive(Clone, PartialEq, Debug)] @@ -36,8 +37,9 @@ pub struct FnSpec<'a> { pub tp: FnType, // Rust function name pub name: &'a syn::Ident, - // Wrapped python name - pub python_name: Option, + // Wrapped python name. This should have been sent through syn::IdentExt::unraw() + // to ensure that any leading r# is removed. + pub python_name: syn::Ident, pub attrs: Vec, pub args: Vec>, pub output: syn::Type, @@ -59,24 +61,8 @@ impl<'a> FnSpec<'a> { allow_custom_name: bool, ) -> syn::Result> { let name = &sig.ident; - let (mut fn_type, fn_attrs, mut python_name) = - parse_attributes(meth_attrs, allow_custom_name)?; - - // "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.to_string(); - if ident.starts_with(prefix) { - python_name = Some(syn::Ident::new(&ident[prefix.len()..], ident.span())) - } - } - } + let MethodAttributes { ty: mut fn_type, args: fn_attrs, mut python_name } = + parse_method_attributes(meth_attrs, allow_custom_name)?; let mut has_self = false; let mut arguments = Vec::new(); @@ -136,12 +122,28 @@ impl<'a> FnSpec<'a> { fn_type = FnType::PySelf(tp); } - let mut parse_erroneous_text_signature = |error_msg: &str| { - let py_name = python_name.as_ref().unwrap_or(name); + // "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 mut parse_erroneous_text_signature = |error_msg: &str| { // try to parse anyway to give better error messages if let Some(text_signature) = - utils::parse_text_signature_attrs(&mut *meth_attrs, py_name)? + utils::parse_text_signature_attrs(meth_attrs, &python_name)? { Err(syn::Error::new_spanned(text_signature, error_msg)) } else { @@ -181,10 +183,6 @@ impl<'a> FnSpec<'a> { }) } - pub fn py_name(&self) -> &syn::Ident { - self.python_name.as_ref().unwrap_or(self.name) - } - pub fn is_args(&self, name: &syn::Ident) -> bool { for s in self.attrs.iter() { if let Argument::VarArgs(ref path) = s { @@ -344,14 +342,20 @@ pub fn check_arg_ty_and_optional<'a>( } } -fn parse_attributes( +#[derive(Clone, PartialEq, Debug)] +struct MethodAttributes { + ty: FnType, + args: Vec, + python_name: Option +} + +fn parse_method_attributes( attrs: &mut Vec, allow_custom_name: bool, -) -> syn::Result<(FnType, Vec, Option)> { +) -> syn::Result { let mut new_attrs = Vec::new(); - let mut spec = Vec::new(); + let mut args = Vec::new(); let mut res: Option = None; - let mut name_with_span = None; let mut property_name = None; for attr in attrs.iter() { @@ -454,77 +458,66 @@ fn parse_attributes( }; } else if path.is_ident("args") { let attrs = PyFunctionAttr::from_meta(nested)?; - spec.extend(attrs.arguments) + args.extend(attrs.arguments) } else { new_attrs.push(attr.clone()) } } - syn::Meta::NameValue(ref nv) if allow_custom_name && nv.path.is_ident("name") => { - if name_with_span.is_some() { - return Err(syn::Error::new_spanned( - &nv.path, - "name can not be specified multiple times", - )); - } - - match &nv.lit { - syn::Lit::Str(s) => name_with_span = Some((s.parse()?, nv.path.span())), - _ => { - return Err(syn::Error::new_spanned( - &nv.lit, - "Expected string literal for method name", - )) - } - } - } syn::Meta::NameValue(_) => new_attrs.push(attr.clone()), } } + attrs.clear(); attrs.extend(new_attrs); - if let Some((_, span)) = &name_with_span { - match &res { - Some(FnType::FnNew) => { - return Err(syn::Error::new( - *span, - "name can not be specified with #[new]", - )) - } - Some(FnType::FnCall) => { - return Err(syn::Error::new( - *span, - "name can not be specified with #[call]", - )) - } - Some(FnType::Getter) => { - return Err(syn::Error::new( - *span, - "name can not be specified for getter", - )) - } - Some(FnType::Setter) => { - return Err(syn::Error::new( - *span, - "name can not be specified for setter", - )) - } + let ty = res.unwrap_or(FnType::Fn); + let python_name = if allow_custom_name { + parse_method_name_attribute(&ty, attrs, property_name)? + } else { + property_name + }; + + Ok(MethodAttributes { ty, args, python_name }) +} + +fn parse_method_name_attribute( + ty: &FnType, + attrs: &mut Vec, + property_name: Option +) -> syn::Result> { + + let name = parse_name_attribute(attrs)?; + + // Reject some invalid combinations + if let Some(name) = &name { + match ty { + FnType::FnNew => return Err(syn::Error::new_spanned( + name, + "name can not be specified with #[new]", + )), + FnType::FnCall => return Err(syn::Error::new_spanned( + name, + "name can not be specified with #[call]", + )), + FnType::Getter => return Err(syn::Error::new_spanned( + name, + "name can not be specified for getter", + )), + FnType::Setter => return Err(syn::Error::new_spanned( + name, + "name can not be specified for setter", + )), _ => {} } } // Thanks to check above we can be sure that this generates the right python name - let python_name = match res { - Some(FnType::FnNew) => Some(syn::Ident::new("__new__", proc_macro2::Span::call_site())), - Some(FnType::FnCall) => Some(syn::Ident::new("__call__", proc_macro2::Span::call_site())), - Some(FnType::Getter) | Some(FnType::Setter) => property_name, - _ => name_with_span.map(|ns| ns.0), - }; - - match res { - Some(tp) => Ok((tp, spec, python_name)), - None => Ok((FnType::Fn, spec, python_name)), - } + Ok(match ty { + FnType::FnNew => Some(syn::Ident::new("__new__", proc_macro2::Span::call_site())), + FnType::FnCall => Some(syn::Ident::new("__call__", proc_macro2::Span::call_site())), + FnType::Getter | FnType::Setter => property_name, + _ => name, + }) } // Replace A with A<_> diff --git a/pyo3-derive-backend/src/module.rs b/pyo3-derive-backend/src/module.rs index 2967594a..badaf37f 100644 --- a/pyo3-derive-backend/src/module.rs +++ b/pyo3-derive-backend/src/module.rs @@ -161,7 +161,7 @@ pub fn add_fn_to_module( let spec = method::FnSpec { tp: method::FnType::Fn, name: &function_wrapper_ident, - python_name: Some(python_name), + python_name, attrs: pyfn_attrs, args: arguments, output: ty, @@ -170,7 +170,7 @@ pub fn add_fn_to_module( let doc = &spec.doc; - let python_name = spec.py_name(); + let python_name = &spec.python_name; let wrapper = function_c_wrapper(&func.sig.ident, &spec); diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index 2af41411..78118380 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -5,6 +5,7 @@ use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, impl_wrap_getter, use crate::utils; use proc_macro2::{Span, TokenStream}; use quote::quote; +use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{parse_quote, Expr, Token}; @@ -455,7 +456,7 @@ fn impl_descriptors( let spec = FnSpec { tp: FnType::Getter, name: &name, - python_name: None, + python_name: name.unraw(), attrs: Vec::new(), args: Vec::new(), output: parse_quote!(PyResult<#field_ty>), @@ -469,7 +470,7 @@ fn impl_descriptors( let spec = FnSpec { tp: FnType::Setter, name: &setter_name, - python_name: Some(name.clone()), + python_name: name.unraw(), attrs: Vec::new(), args: vec![FnArg { name: &name, diff --git a/pyo3-derive-backend/src/pyfunction.rs b/pyo3-derive-backend/src/pyfunction.rs index 24a0eb26..674fd52c 100644 --- a/pyo3-derive-backend/src/pyfunction.rs +++ b/pyo3-derive-backend/src/pyfunction.rs @@ -1,8 +1,12 @@ // Copyright (c) 2017-present PyO3 Project and Contributors +use syn::ext::IdentExt; use syn::parse::ParseBuffer; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; use syn::{NestedMeta, Path}; +use proc_macro2::TokenStream; +use crate::module::add_fn_to_module; #[derive(Debug, Clone, PartialEq)] pub enum Argument { @@ -193,6 +197,47 @@ impl PyFunctionAttr { } } +pub fn parse_name_attribute(attrs: &mut Vec) -> syn::Result> { + let mut name_attrs = Vec::new(); + + // Using retain will extract all name attributes from the attribute list + attrs.retain(|attr| { + match attr.parse_meta() { + Ok(syn::Meta::NameValue(ref nv)) if nv.path.is_ident("name") => { + name_attrs.push((nv.lit.clone(), attr.span())); + false + } + _ => true + } + }); + + let mut name = None; + + for (lit, span) in name_attrs { + if name.is_some() { + return Err(syn::Error::new(span, "#[name] can not be specified multiple times")) + } + + name = match lit { + syn::Lit::Str(s) => { + let mut ident: syn::Ident = s.parse()?; + // This span is the whole attribute span, which is nicer for reporting errors. + ident.set_span(span); + Some(ident) + }, + _ => return Err(syn::Error::new(span, "Expected string literal for #[name] argument")) + }; + } + + Ok(name) +} + +pub fn build_py_function(ast: &mut syn::ItemFn, args: PyFunctionAttr) -> syn::Result { + let python_name = + parse_name_attribute(&mut ast.attrs)?.unwrap_or_else(|| ast.sig.ident.unraw()); + Ok(add_fn_to_module(ast, python_name, args.arguments)) +} + #[cfg(test)] mod test { use super::{Argument, PyFunctionAttr}; diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index 3afff40e..ce051d29 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -72,7 +72,7 @@ fn impl_wrap_common( slf: TokenStream, body: TokenStream, ) -> TokenStream { - let py_name = spec.py_name(); + let python_name = &spec.python_name; if spec.args.is_empty() && noargs { quote! { unsafe extern "C" fn __wrap( @@ -80,7 +80,7 @@ fn impl_wrap_common( ) -> *mut pyo3::ffi::PyObject { const _LOCATION: &'static str = concat!( - stringify!(#cls), ".", stringify!(#py_name), "()"); + stringify!(#cls), ".", stringify!(#python_name), "()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); #slf @@ -102,7 +102,7 @@ fn impl_wrap_common( _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject { const _LOCATION: &'static str = concat!( - stringify!(#cls), ".", stringify!(#py_name), "()"); + stringify!(#cls), ".", stringify!(#python_name), "()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); #slf @@ -120,7 +120,7 @@ fn impl_wrap_common( /// Generate function wrapper for protocol method (PyCFunction, PyCFunctionWithKeywords) pub fn impl_proto_wrap(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { - let py_name = spec.py_name(); + let python_name = &spec.python_name; let cb = impl_call(cls, &spec); let body = impl_arg_params(&spec, cb); @@ -131,7 +131,7 @@ pub fn impl_proto_wrap(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { _args: *mut pyo3::ffi::PyObject, _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject { - const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#py_name),"()"); + const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf); @@ -149,7 +149,7 @@ pub fn impl_proto_wrap(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { /// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords) pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { let name = &spec.name; - let py_name = spec.py_name(); + let python_name = &spec.python_name; let names: Vec = get_arg_names(&spec); let cb = quote! { #cls::#name(&_obj, #(#names),*) }; @@ -164,7 +164,7 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { { use pyo3::type_object::PyTypeInfo; - const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#py_name),"()"); + const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_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) { @@ -194,7 +194,7 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { /// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords) pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { let name = &spec.name; - let py_name = spec.py_name(); + let python_name = &spec.python_name; let names: Vec = get_arg_names(&spec); let cb = quote! { #cls::#name(&_cls, #(#names),*) }; @@ -207,7 +207,7 @@ pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { _args: *mut pyo3::ffi::PyObject, _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject { - const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#py_name),"()"); + const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); let _cls = pyo3::types::PyType::from_type_ptr(_py, _cls as *mut pyo3::ffi::PyTypeObject); @@ -225,7 +225,7 @@ pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { /// Generate static method wrapper (PyCFunction, PyCFunctionWithKeywords) pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { let name = &spec.name; - let py_name = spec.py_name(); + let python_name = &spec.python_name; let names: Vec = get_arg_names(&spec); let cb = quote! { #cls::#name(#(#names),*) }; @@ -238,7 +238,7 @@ pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { _args: *mut pyo3::ffi::PyObject, _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject { - const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#py_name),"()"); + const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); let _args = _py.from_borrowed_ptr::(_args); @@ -266,7 +266,7 @@ pub(crate) fn impl_wrap_getter(cls: &syn::Type, spec: &FnSpec) -> syn::Result syn::Result *mut pyo3::ffi::PyObject { - const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#py_name),"()"); + const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); @@ -302,7 +302,7 @@ pub(crate) fn impl_wrap_getter(cls: &syn::Type, spec: &FnSpec) -> syn::Result) -> syn::Result { let name = &spec.name; - let py_name = spec.py_name(); + let python_name = &spec.python_name; let val_ty = match &*spec.args { [] => { @@ -326,7 +326,7 @@ pub(crate) fn impl_wrap_setter(cls: &syn::Type, spec: &FnSpec<'_>) -> syn::Resul _slf: *mut pyo3::ffi::PyObject, _value: *mut pyo3::ffi::PyObject, _: *mut ::std::os::raw::c_void) -> pyo3::libc::c_int { - const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#py_name),"()"); + const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf); @@ -517,7 +517,7 @@ fn impl_arg_param( } pub fn impl_py_method_def(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream { - let py_name = spec.py_name(); + let python_name = &spec.python_name; let doc = &spec.doc; if spec.args.is_empty() { quote! { @@ -525,7 +525,7 @@ pub fn impl_py_method_def(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream { #wrapper pyo3::class::PyMethodDef { - ml_name: stringify!(#py_name), + ml_name: stringify!(#python_name), ml_meth: pyo3::class::PyMethodType::PyNoArgsFunction(__wrap), ml_flags: pyo3::ffi::METH_NOARGS, ml_doc: #doc, @@ -538,7 +538,7 @@ pub fn impl_py_method_def(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream { #wrapper pyo3::class::PyMethodDef { - ml_name: stringify!(#py_name), + ml_name: stringify!(#python_name), ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap), ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS, ml_doc: #doc, @@ -549,14 +549,14 @@ pub fn impl_py_method_def(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream { } pub fn impl_py_method_def_new(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream { - let py_name = spec.py_name(); + let python_name = &spec.python_name; let doc = &spec.doc; quote! { pyo3::class::PyMethodDefType::New({ #wrapper pyo3::class::PyMethodDef { - ml_name: stringify!(#py_name), + ml_name: stringify!(#python_name), ml_meth: pyo3::class::PyMethodType::PyNewFunc(__wrap), ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS, ml_doc: #doc, @@ -566,14 +566,14 @@ pub fn impl_py_method_def_new(spec: &FnSpec, wrapper: &TokenStream) -> TokenStre } pub fn impl_py_method_def_class(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream { - let py_name = spec.py_name(); + let python_name = &spec.python_name; let doc = &spec.doc; quote! { pyo3::class::PyMethodDefType::Class({ #wrapper pyo3::class::PyMethodDef { - ml_name: stringify!(#py_name), + ml_name: stringify!(#python_name), ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap), ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS | pyo3::ffi::METH_CLASS, @@ -584,14 +584,14 @@ pub fn impl_py_method_def_class(spec: &FnSpec, wrapper: &TokenStream) -> TokenSt } pub fn impl_py_method_def_static(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream { - let py_name = spec.py_name(); + let python_name = &spec.python_name; let doc = &spec.doc; quote! { pyo3::class::PyMethodDefType::Static({ #wrapper pyo3::class::PyMethodDef { - ml_name: stringify!(#py_name), + ml_name: stringify!(#python_name), ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap), ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS | pyo3::ffi::METH_STATIC, ml_doc: #doc, @@ -601,14 +601,14 @@ pub fn impl_py_method_def_static(spec: &FnSpec, wrapper: &TokenStream) -> TokenS } pub fn impl_py_method_def_call(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream { - let py_name = spec.py_name(); + let python_name = &spec.python_name; let doc = &spec.doc; quote! { pyo3::class::PyMethodDefType::Call({ #wrapper pyo3::class::PyMethodDef { - ml_name: stringify!(#py_name), + ml_name: stringify!(#python_name), ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap), ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS, ml_doc: #doc, @@ -618,7 +618,7 @@ pub fn impl_py_method_def_call(spec: &FnSpec, wrapper: &TokenStream) -> TokenStr } pub(crate) fn impl_py_setter_def(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream { - let py_name = &spec.py_name(); + let python_name = &&spec.python_name; let doc = &spec.doc; quote! { @@ -626,7 +626,7 @@ pub(crate) fn impl_py_setter_def(spec: &FnSpec, wrapper: &TokenStream) -> TokenS #wrapper pyo3::class::PySetterDef { - name: stringify!(#py_name), + name: stringify!(#python_name), meth: __wrap, doc: #doc, } @@ -635,7 +635,7 @@ pub(crate) fn impl_py_setter_def(spec: &FnSpec, wrapper: &TokenStream) -> TokenS } pub(crate) fn impl_py_getter_def(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream { - let py_name = &spec.py_name(); + let python_name = &&spec.python_name; let doc = &spec.doc; quote! { @@ -643,7 +643,7 @@ pub(crate) fn impl_py_getter_def(spec: &FnSpec, wrapper: &TokenStream) -> TokenS #wrapper pyo3::class::PyGetterDef { - name: stringify!(#py_name), + name: stringify!(#python_name), meth: __wrap, doc: #doc, } diff --git a/pyo3cls/src/lib.rs b/pyo3cls/src/lib.rs index 2a4ceeb2..0ba93c99 100644 --- a/pyo3cls/src/lib.rs +++ b/pyo3cls/src/lib.rs @@ -4,13 +4,11 @@ extern crate proc_macro; use proc_macro::TokenStream; -use proc_macro2::Span; use pyo3_derive_backend::{ - add_fn_to_module, build_py_class, build_py_methods, build_py_proto, get_doc, + build_py_class, build_py_function, build_py_methods, build_py_proto, get_doc, process_functions_in_module, py_init, PyClassArgs, PyFunctionAttr, }; use quote::quote; -use syn::ext::IdentExt; use syn::parse_macro_input; /// Internally, this proc macro create a new c function called `PyInit_{my_module}` @@ -83,9 +81,7 @@ pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as syn::ItemFn); let args = parse_macro_input!(attr as PyFunctionAttr); - // TODO: Support #[name = "..."] attribute? - let python_name = syn::Ident::new(&ast.sig.ident.unraw().to_string(), Span::call_site()); - let expanded = add_fn_to_module(&mut ast, python_name, args.arguments); + let expanded = build_py_function(&mut ast, args).unwrap_or_else(|e| e.to_compile_error()); quote!( #ast diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index be0f202e..d6cebe9f 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -68,6 +68,23 @@ fn custom_names() { py_assert!(py, typeobj, "not hasattr(typeobj, 'bar_static')"); } +#[pyclass] +struct RawIdents {} + +#[pymethods] +impl RawIdents { + fn r#fn(&self) { } +} + +#[test] +fn test_raw_idents() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let typeobj = py.get_type::(); + py_assert!(py, typeobj, "not hasattr(typeobj, 'r#fn')"); + py_assert!(py, typeobj, "hasattr(typeobj, 'fn')"); +} + #[pyclass] struct EmptyClassInModule {} diff --git a/tests/test_module.rs b/tests/test_module.rs index cda0d835..96c8f6f9 100644 --- a/tests/test_module.rs +++ b/tests/test_module.rs @@ -148,6 +148,32 @@ fn test_raw_idents() { py_assert!(py, module, "module.move() == 42"); } +#[pyfunction] +#[name = "foobar"] +fn custom_named_fn() -> usize { + 42 +} + +#[pymodule] +fn foobar_module(_py: Python, module: &PyModule) -> PyResult<()> { + use pyo3::wrap_pyfunction; + + module.add_wrapped(wrap_pyfunction!(custom_named_fn)) +} + +#[test] +fn test_custom_names() { + use pyo3::wrap_pymodule; + + let gil = Python::acquire_gil(); + let py = gil.python(); + + let module = wrap_pymodule!(foobar_module)(py); + + py_assert!(py, module, "not hasattr(module, 'custom_named_fn')"); + py_assert!(py, module, "module.foobar() == 42"); +} + #[pyfunction] fn subfunction() -> String { "Subfunction".to_string() diff --git a/tests/ui/invalid_pymethod_names.stderr b/tests/ui/invalid_pymethod_names.stderr index eb1f15a0..dcd0a848 100644 --- a/tests/ui/invalid_pymethod_names.stderr +++ b/tests/ui/invalid_pymethod_names.stderr @@ -1,29 +1,17 @@ error: name can not be specified for getter - --> $DIR/invalid_pymethod_names.rs:10:7 + --> $DIR/invalid_pymethod_names.rs:10:5 | 10 | #[name = "num"] - | ^^^^ + | ^^^^^^^^^^^^^^^ -error: name can not be specified multiple times - --> $DIR/invalid_pymethod_names.rs:18:7 +error: #[name] can not be specified multiple times + --> $DIR/invalid_pymethod_names.rs:18:5 | 18 | #[name = "bar"] - | ^^^^ + | ^^^^^^^^^^^^^^^ error: name can not be specified with #[new] - --> $DIR/invalid_pymethod_names.rs:24:7 + --> $DIR/invalid_pymethod_names.rs:24:5 | 24 | #[name = "makenew"] - | ^^^^ - -error: cannot find attribute `name` in this scope - --> $DIR/invalid_pymethod_names.rs:17:7 - | -17 | #[name = "foo"] - | ^^^^ - -error: cannot find attribute `name` in this scope - --> $DIR/invalid_pymethod_names.rs:18:7 - | -18 | #[name = "bar"] - | ^^^^ + | ^^^^^^^^^^^^^^^^^^^