Skip to content

Commit 4b18830

Browse files
committed
Add #[name] attribute support for #[pyfunction]
1 parent 881cb0a commit 4b18830

File tree

11 files changed

+215
-149
lines changed

11 files changed

+215
-149
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## Unreleased
99

10-
* Support for `#[name = "foo"]` attribute in `#[pymethods]`. [#692](https://github.com/PyO3/pyo3/pull/692)
10+
* Support for `#[name = "foo"]` attribute for `#[pyfunction]` and in `#[pymethods]`. [#692](https://github.com/PyO3/pyo3/pull/692)
1111

1212
## [0.8.4]
1313

pyo3-derive-backend/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mod utils;
1616

1717
pub use module::{add_fn_to_module, process_functions_in_module, py_init};
1818
pub use pyclass::{build_py_class, PyClassArgs};
19-
pub use pyfunction::PyFunctionAttr;
19+
pub use pyfunction::{build_py_function, PyFunctionAttr};
2020
pub use pyimpl::{build_py_methods, impl_methods};
2121
pub use pyproto::build_py_proto;
2222
pub use utils::get_doc;

pyo3-derive-backend/src/method.rs

Lines changed: 81 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// Copyright (c) 2017-present PyO3 Project and Contributors
22

33
use crate::pyfunction::Argument;
4-
use crate::pyfunction::PyFunctionAttr;
4+
use crate::pyfunction::{PyFunctionAttr, parse_name_attribute};
55
use crate::utils;
66
use proc_macro2::TokenStream;
77
use quote::quote;
88
use quote::ToTokens;
9+
use syn::ext::IdentExt;
910
use syn::spanned::Spanned;
1011

1112
#[derive(Clone, PartialEq, Debug)]
@@ -36,8 +37,9 @@ pub struct FnSpec<'a> {
3637
pub tp: FnType,
3738
// Rust function name
3839
pub name: &'a syn::Ident,
39-
// Wrapped python name
40-
pub python_name: Option<syn::Ident>,
40+
// Wrapped python name. This should have been sent through syn::IdentExt::unraw()
41+
// to ensure that any leading r# is removed.
42+
pub python_name: syn::Ident,
4143
pub attrs: Vec<Argument>,
4244
pub args: Vec<FnArg<'a>>,
4345
pub output: syn::Type,
@@ -59,24 +61,8 @@ impl<'a> FnSpec<'a> {
5961
allow_custom_name: bool,
6062
) -> syn::Result<FnSpec<'a>> {
6163
let name = &sig.ident;
62-
let (mut fn_type, fn_attrs, mut python_name) =
63-
parse_attributes(meth_attrs, allow_custom_name)?;
64-
65-
// "Tweak" getter / setter names: strip off set_ and get_ if needed
66-
if let FnType::Getter | FnType::Setter = &fn_type {
67-
if python_name.is_none() {
68-
let prefix = match &fn_type {
69-
FnType::Getter => "get_",
70-
FnType::Setter => "set_",
71-
_ => unreachable!(),
72-
};
73-
74-
let ident = sig.ident.to_string();
75-
if ident.starts_with(prefix) {
76-
python_name = Some(syn::Ident::new(&ident[prefix.len()..], ident.span()))
77-
}
78-
}
79-
}
64+
let MethodAttributes { ty: mut fn_type, args: fn_attrs, mut python_name } =
65+
parse_method_attributes(meth_attrs, allow_custom_name)?;
8066

8167
let mut has_self = false;
8268
let mut arguments = Vec::new();
@@ -136,12 +122,28 @@ impl<'a> FnSpec<'a> {
136122
fn_type = FnType::PySelf(tp);
137123
}
138124

139-
let mut parse_erroneous_text_signature = |error_msg: &str| {
140-
let py_name = python_name.as_ref().unwrap_or(name);
125+
// "Tweak" getter / setter names: strip off set_ and get_ if needed
126+
if let FnType::Getter | FnType::Setter = &fn_type {
127+
if python_name.is_none() {
128+
let prefix = match &fn_type {
129+
FnType::Getter => "get_",
130+
FnType::Setter => "set_",
131+
_ => unreachable!(),
132+
};
133+
134+
let ident = sig.ident.unraw().to_string();
135+
if ident.starts_with(prefix) {
136+
python_name = Some(syn::Ident::new(&ident[prefix.len()..], ident.span()))
137+
}
138+
}
139+
}
140+
141+
let python_name = python_name.unwrap_or_else(|| name.unraw());
141142

143+
let mut parse_erroneous_text_signature = |error_msg: &str| {
142144
// try to parse anyway to give better error messages
143145
if let Some(text_signature) =
144-
utils::parse_text_signature_attrs(&mut *meth_attrs, py_name)?
146+
utils::parse_text_signature_attrs(meth_attrs, &python_name)?
145147
{
146148
Err(syn::Error::new_spanned(text_signature, error_msg))
147149
} else {
@@ -181,10 +183,6 @@ impl<'a> FnSpec<'a> {
181183
})
182184
}
183185

184-
pub fn py_name(&self) -> &syn::Ident {
185-
self.python_name.as_ref().unwrap_or(self.name)
186-
}
187-
188186
pub fn is_args(&self, name: &syn::Ident) -> bool {
189187
for s in self.attrs.iter() {
190188
if let Argument::VarArgs(ref path) = s {
@@ -344,14 +342,20 @@ pub fn check_arg_ty_and_optional<'a>(
344342
}
345343
}
346344

347-
fn parse_attributes(
345+
#[derive(Clone, PartialEq, Debug)]
346+
struct MethodAttributes {
347+
ty: FnType,
348+
args: Vec<Argument>,
349+
python_name: Option<syn::Ident>
350+
}
351+
352+
fn parse_method_attributes(
348353
attrs: &mut Vec<syn::Attribute>,
349354
allow_custom_name: bool,
350-
) -> syn::Result<(FnType, Vec<Argument>, Option<syn::Ident>)> {
355+
) -> syn::Result<MethodAttributes> {
351356
let mut new_attrs = Vec::new();
352-
let mut spec = Vec::new();
357+
let mut args = Vec::new();
353358
let mut res: Option<FnType> = None;
354-
let mut name_with_span = None;
355359
let mut property_name = None;
356360

357361
for attr in attrs.iter() {
@@ -454,77 +458,66 @@ fn parse_attributes(
454458
};
455459
} else if path.is_ident("args") {
456460
let attrs = PyFunctionAttr::from_meta(nested)?;
457-
spec.extend(attrs.arguments)
461+
args.extend(attrs.arguments)
458462
} else {
459463
new_attrs.push(attr.clone())
460464
}
461465
}
462-
syn::Meta::NameValue(ref nv) if allow_custom_name && nv.path.is_ident("name") => {
463-
if name_with_span.is_some() {
464-
return Err(syn::Error::new_spanned(
465-
&nv.path,
466-
"name can not be specified multiple times",
467-
));
468-
}
469-
470-
match &nv.lit {
471-
syn::Lit::Str(s) => name_with_span = Some((s.parse()?, nv.path.span())),
472-
_ => {
473-
return Err(syn::Error::new_spanned(
474-
&nv.lit,
475-
"Expected string literal for method name",
476-
))
477-
}
478-
}
479-
}
480466
syn::Meta::NameValue(_) => new_attrs.push(attr.clone()),
481467
}
482468
}
469+
483470
attrs.clear();
484471
attrs.extend(new_attrs);
485472

486-
if let Some((_, span)) = &name_with_span {
487-
match &res {
488-
Some(FnType::FnNew) => {
489-
return Err(syn::Error::new(
490-
*span,
491-
"name can not be specified with #[new]",
492-
))
493-
}
494-
Some(FnType::FnCall) => {
495-
return Err(syn::Error::new(
496-
*span,
497-
"name can not be specified with #[call]",
498-
))
499-
}
500-
Some(FnType::Getter) => {
501-
return Err(syn::Error::new(
502-
*span,
503-
"name can not be specified for getter",
504-
))
505-
}
506-
Some(FnType::Setter) => {
507-
return Err(syn::Error::new(
508-
*span,
509-
"name can not be specified for setter",
510-
))
511-
}
473+
let ty = res.unwrap_or(FnType::Fn);
474+
let python_name = if allow_custom_name {
475+
parse_method_name_attribute(&ty, attrs, property_name)?
476+
} else {
477+
property_name
478+
};
479+
480+
Ok(MethodAttributes { ty, args, python_name })
481+
}
482+
483+
fn parse_method_name_attribute(
484+
ty: &FnType,
485+
attrs: &mut Vec<syn::Attribute>,
486+
property_name: Option<syn::Ident>
487+
) -> syn::Result<Option<syn::Ident>> {
488+
489+
let name = parse_name_attribute(attrs)?;
490+
491+
// Reject some invalid combinations
492+
if let Some(name) = &name {
493+
match ty {
494+
FnType::FnNew => return Err(syn::Error::new_spanned(
495+
name,
496+
"name can not be specified with #[new]",
497+
)),
498+
FnType::FnCall => return Err(syn::Error::new_spanned(
499+
name,
500+
"name can not be specified with #[call]",
501+
)),
502+
FnType::Getter => return Err(syn::Error::new_spanned(
503+
name,
504+
"name can not be specified for getter",
505+
)),
506+
FnType::Setter => return Err(syn::Error::new_spanned(
507+
name,
508+
"name can not be specified for setter",
509+
)),
512510
_ => {}
513511
}
514512
}
515513

516514
// Thanks to check above we can be sure that this generates the right python name
517-
let python_name = match res {
518-
Some(FnType::FnNew) => Some(syn::Ident::new("__new__", proc_macro2::Span::call_site())),
519-
Some(FnType::FnCall) => Some(syn::Ident::new("__call__", proc_macro2::Span::call_site())),
520-
Some(FnType::Getter) | Some(FnType::Setter) => property_name,
521-
_ => name_with_span.map(|ns| ns.0),
522-
};
523-
524-
match res {
525-
Some(tp) => Ok((tp, spec, python_name)),
526-
None => Ok((FnType::Fn, spec, python_name)),
527-
}
515+
Ok(match ty {
516+
FnType::FnNew => Some(syn::Ident::new("__new__", proc_macro2::Span::call_site())),
517+
FnType::FnCall => Some(syn::Ident::new("__call__", proc_macro2::Span::call_site())),
518+
FnType::Getter | FnType::Setter => property_name,
519+
_ => name,
520+
})
528521
}
529522

530523
// Replace A<Self> with A<_>

pyo3-derive-backend/src/module.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ pub fn add_fn_to_module(
161161
let spec = method::FnSpec {
162162
tp: method::FnType::Fn,
163163
name: &function_wrapper_ident,
164-
python_name: Some(python_name),
164+
python_name,
165165
attrs: pyfn_attrs,
166166
args: arguments,
167167
output: ty,
@@ -170,7 +170,7 @@ pub fn add_fn_to_module(
170170

171171
let doc = &spec.doc;
172172

173-
let python_name = spec.py_name();
173+
let python_name = &spec.python_name;
174174

175175
let wrapper = function_c_wrapper(&func.sig.ident, &spec);
176176

pyo3-derive-backend/src/pyclass.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, impl_wrap_getter,
55
use crate::utils;
66
use proc_macro2::{Span, TokenStream};
77
use quote::quote;
8+
use syn::ext::IdentExt;
89
use syn::parse::{Parse, ParseStream};
910
use syn::punctuated::Punctuated;
1011
use syn::{parse_quote, Expr, Token};
@@ -455,7 +456,7 @@ fn impl_descriptors(
455456
let spec = FnSpec {
456457
tp: FnType::Getter,
457458
name: &name,
458-
python_name: None,
459+
python_name: name.unraw(),
459460
attrs: Vec::new(),
460461
args: Vec::new(),
461462
output: parse_quote!(PyResult<#field_ty>),
@@ -469,7 +470,7 @@ fn impl_descriptors(
469470
let spec = FnSpec {
470471
tp: FnType::Setter,
471472
name: &setter_name,
472-
python_name: Some(name.clone()),
473+
python_name: name.unraw(),
473474
attrs: Vec::new(),
474475
args: vec![FnArg {
475476
name: &name,

pyo3-derive-backend/src/pyfunction.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
// Copyright (c) 2017-present PyO3 Project and Contributors
22

3+
use syn::ext::IdentExt;
34
use syn::parse::ParseBuffer;
45
use syn::punctuated::Punctuated;
6+
use syn::spanned::Spanned;
57
use syn::{NestedMeta, Path};
8+
use proc_macro2::TokenStream;
9+
use crate::module::add_fn_to_module;
610

711
#[derive(Debug, Clone, PartialEq)]
812
pub enum Argument {
@@ -193,6 +197,47 @@ impl PyFunctionAttr {
193197
}
194198
}
195199

200+
pub fn parse_name_attribute(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<syn::Ident>> {
201+
let mut name_attrs = Vec::new();
202+
203+
// Using retain will extract all name attributes from the attribute list
204+
attrs.retain(|attr| {
205+
match attr.parse_meta() {
206+
Ok(syn::Meta::NameValue(ref nv)) if nv.path.is_ident("name") => {
207+
name_attrs.push((nv.lit.clone(), attr.span()));
208+
false
209+
}
210+
_ => true
211+
}
212+
});
213+
214+
let mut name = None;
215+
216+
for (lit, span) in name_attrs {
217+
if name.is_some() {
218+
return Err(syn::Error::new(span, "#[name] can not be specified multiple times"))
219+
}
220+
221+
name = match lit {
222+
syn::Lit::Str(s) => {
223+
let mut ident: syn::Ident = s.parse()?;
224+
// This span is the whole attribute span, which is nicer for reporting errors.
225+
ident.set_span(span);
226+
Some(ident)
227+
},
228+
_ => return Err(syn::Error::new(span, "Expected string literal for #[name] argument"))
229+
};
230+
}
231+
232+
Ok(name)
233+
}
234+
235+
pub fn build_py_function(ast: &mut syn::ItemFn, args: PyFunctionAttr) -> syn::Result<TokenStream> {
236+
let python_name =
237+
parse_name_attribute(&mut ast.attrs)?.unwrap_or_else(|| ast.sig.ident.unraw());
238+
Ok(add_fn_to_module(ast, python_name, args.arguments))
239+
}
240+
196241
#[cfg(test)]
197242
mod test {
198243
use super::{Argument, PyFunctionAttr};

0 commit comments

Comments
 (0)