Skip to content

Commit c8cb3ad

Browse files
authored
Merge pull request #692 from davidhewitt/override-method-names
Add #[name = "foo"] attribute to #[pymethods]
2 parents 1518767 + b245e71 commit c8cb3ad

17 files changed

+504
-302
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## Unreleased
99

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

1214
### Added

pyo3-derive-backend/src/func.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,16 @@ pub enum MethodProto {
5555
},
5656
}
5757

58-
impl PartialEq<str> for MethodProto {
59-
fn eq(&self, name: &str) -> bool {
58+
impl MethodProto {
59+
pub fn name(&self) -> &str {
6060
match *self {
61-
MethodProto::Free { name: n, .. } => n == name,
62-
MethodProto::Unary { name: n, .. } => n == name,
63-
MethodProto::Binary { name: n, .. } => n == name,
64-
MethodProto::BinaryS { name: n, .. } => n == name,
65-
MethodProto::Ternary { name: n, .. } => n == name,
66-
MethodProto::TernaryS { name: n, .. } => n == name,
67-
MethodProto::Quaternary { name: n, .. } => n == name,
61+
MethodProto::Free { ref name, .. } => name,
62+
MethodProto::Unary { ref name, .. } => name,
63+
MethodProto::Binary { ref name, .. } => name,
64+
MethodProto::BinaryS { ref name, .. } => name,
65+
MethodProto::Ternary { ref name, .. } => name,
66+
MethodProto::TernaryS { ref name, .. } => name,
67+
MethodProto::Quaternary { ref name, .. } => name,
6868
}
6969
}
7070
}

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: 162 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +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::{parse_name_attribute, PyFunctionAttr};
5+
use crate::utils;
56
use proc_macro2::TokenStream;
67
use quote::quote;
78
use quote::ToTokens;
9+
use syn::ext::IdentExt;
810
use syn::spanned::Spanned;
911

1012
#[derive(Clone, PartialEq, Debug)]
@@ -20,8 +22,8 @@ pub struct FnArg<'a> {
2022

2123
#[derive(Clone, PartialEq, Debug)]
2224
pub enum FnType {
23-
Getter(Option<String>),
24-
Setter(Option<String>),
25+
Getter,
26+
Setter,
2527
Fn,
2628
FnNew,
2729
FnCall,
@@ -33,9 +35,15 @@ pub enum FnType {
3335
#[derive(Clone, PartialEq, Debug)]
3436
pub struct FnSpec<'a> {
3537
pub tp: FnType,
38+
// Rust function name
39+
pub name: &'a syn::Ident,
40+
// Wrapped python name. This should not have any leading r#.
41+
// r# can be removed by syn::ext::IdentExt::unraw()
42+
pub python_name: syn::Ident,
3643
pub attrs: Vec<Argument>,
3744
pub args: Vec<FnArg<'a>>,
3845
pub output: syn::Type,
46+
pub doc: syn::LitStr,
3947
}
4048

4149
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
@@ -48,11 +56,16 @@ pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
4856
impl<'a> FnSpec<'a> {
4957
/// Parser function signature and function attributes
5058
pub fn parse(
51-
name: &'a syn::Ident,
5259
sig: &'a syn::Signature,
5360
meth_attrs: &mut Vec<syn::Attribute>,
61+
allow_custom_name: bool,
5462
) -> syn::Result<FnSpec<'a>> {
55-
let (mut fn_type, fn_attrs) = parse_attributes(meth_attrs)?;
63+
let name = &sig.ident;
64+
let MethodAttributes {
65+
ty: mut fn_type,
66+
args: fn_attrs,
67+
mut python_name,
68+
} = parse_method_attributes(meth_attrs, allow_custom_name)?;
5669

5770
let mut has_self = false;
5871
let mut arguments = Vec::new();
@@ -112,11 +125,58 @@ impl<'a> FnSpec<'a> {
112125
fn_type = FnType::PySelf(tp);
113126
}
114127

128+
// "Tweak" getter / setter names: strip off set_ and get_ if needed
129+
if let FnType::Getter | FnType::Setter = &fn_type {
130+
if python_name.is_none() {
131+
let prefix = match &fn_type {
132+
FnType::Getter => "get_",
133+
FnType::Setter => "set_",
134+
_ => unreachable!(),
135+
};
136+
137+
let ident = sig.ident.unraw().to_string();
138+
if ident.starts_with(prefix) {
139+
python_name = Some(syn::Ident::new(&ident[prefix.len()..], ident.span()))
140+
}
141+
}
142+
}
143+
144+
let python_name = python_name.unwrap_or_else(|| name.unraw());
145+
146+
let mut parse_erroneous_text_signature = |error_msg: &str| {
147+
// try to parse anyway to give better error messages
148+
if let Some(text_signature) =
149+
utils::parse_text_signature_attrs(meth_attrs, &python_name)?
150+
{
151+
Err(syn::Error::new_spanned(text_signature, error_msg))
152+
} else {
153+
Ok(None)
154+
}
155+
};
156+
157+
let text_signature = match &fn_type {
158+
FnType::Fn | FnType::PySelf(_) | FnType::FnClass | FnType::FnStatic => {
159+
utils::parse_text_signature_attrs(&mut *meth_attrs, name)?
160+
}
161+
FnType::FnNew => parse_erroneous_text_signature(
162+
"text_signature not allowed on __new__; if you want to add a signature on \
163+
__new__, put it on the struct definition instead",
164+
)?,
165+
FnType::FnCall | FnType::Getter | FnType::Setter => {
166+
parse_erroneous_text_signature("text_signature not allowed with this attribute")?
167+
}
168+
};
169+
170+
let doc = utils::get_doc(&meth_attrs, text_signature, true)?;
171+
115172
Ok(FnSpec {
116173
tp: fn_type,
174+
name,
175+
python_name,
117176
attrs: fn_attrs,
118177
args: arguments,
119178
output: ty,
179+
doc,
120180
})
121181
}
122182

@@ -279,10 +339,21 @@ pub fn check_arg_ty_and_optional<'a>(
279339
}
280340
}
281341

282-
fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<(FnType, Vec<Argument>)> {
342+
#[derive(Clone, PartialEq, Debug)]
343+
struct MethodAttributes {
344+
ty: FnType,
345+
args: Vec<Argument>,
346+
python_name: Option<syn::Ident>,
347+
}
348+
349+
fn parse_method_attributes(
350+
attrs: &mut Vec<syn::Attribute>,
351+
allow_custom_name: bool,
352+
) -> syn::Result<MethodAttributes> {
283353
let mut new_attrs = Vec::new();
284-
let mut spec = Vec::new();
354+
let mut args = Vec::new();
285355
let mut res: Option<FnType> = None;
356+
let mut property_name = None;
286357

287358
for attr in attrs.iter() {
288359
match attr.parse_meta()? {
@@ -302,15 +373,21 @@ fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<(FnType, Vec
302373
res = Some(FnType::FnStatic)
303374
} else if name.is_ident("setter") || name.is_ident("getter") {
304375
if let syn::AttrStyle::Inner(_) = attr.style {
305-
panic!("Inner style attribute is not supported for setter and getter");
376+
return Err(syn::Error::new_spanned(
377+
attr,
378+
"Inner style attribute is not supported for setter and getter",
379+
));
306380
}
307381
if res != None {
308-
panic!("setter/getter attribute can not be used mutiple times");
382+
return Err(syn::Error::new_spanned(
383+
attr,
384+
"setter/getter attribute can not be used mutiple times",
385+
));
309386
}
310387
if name.is_ident("setter") {
311-
res = Some(FnType::Setter(None))
388+
res = Some(FnType::Setter)
312389
} else {
313-
res = Some(FnType::Getter(None))
390+
res = Some(FnType::Getter)
314391
}
315392
} else {
316393
new_attrs.push(attr.clone())
@@ -332,58 +409,105 @@ fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<(FnType, Vec
332409
res = Some(FnType::FnCall)
333410
} else if path.is_ident("setter") || path.is_ident("getter") {
334411
if let syn::AttrStyle::Inner(_) = attr.style {
335-
panic!(
336-
"Inner style attribute is not
337-
supported for setter and getter"
338-
);
412+
return Err(syn::Error::new_spanned(
413+
attr,
414+
"Inner style attribute is not supported for setter and getter",
415+
));
339416
}
340417
if res != None {
341-
panic!("setter/getter attribute can not be used mutiple times");
418+
return Err(syn::Error::new_spanned(
419+
attr,
420+
"setter/getter attribute can not be used mutiple times",
421+
));
342422
}
343423
if nested.len() != 1 {
344-
panic!("setter/getter requires one value");
424+
return Err(syn::Error::new_spanned(
425+
attr,
426+
"setter/getter requires one value",
427+
));
345428
}
346-
match nested.first().unwrap() {
347-
syn::NestedMeta::Meta(syn::Meta::Path(ref w)) => {
348-
if path.is_ident("setter") {
349-
res = Some(FnType::Setter(Some(w.segments[0].ident.to_string())))
350-
} else {
351-
res = Some(FnType::Getter(Some(w.segments[0].ident.to_string())))
352-
}
429+
430+
res = if path.is_ident("setter") {
431+
Some(FnType::Setter)
432+
} else {
433+
Some(FnType::Getter)
434+
};
435+
436+
property_name = match nested.first().unwrap() {
437+
syn::NestedMeta::Meta(syn::Meta::Path(ref w)) if w.segments.len() == 1 => {
438+
Some(w.segments[0].ident.clone())
353439
}
354440
syn::NestedMeta::Lit(ref lit) => match *lit {
355-
syn::Lit::Str(ref s) => {
356-
if path.is_ident("setter") {
357-
res = Some(FnType::Setter(Some(s.value())))
358-
} else {
359-
res = Some(FnType::Getter(Some(s.value())))
360-
}
361-
}
441+
syn::Lit::Str(ref s) => Some(s.parse()?),
362442
_ => {
363-
panic!("setter/getter attribute requires str value");
443+
return Err(syn::Error::new_spanned(
444+
lit,
445+
"setter/getter attribute requires str value",
446+
))
364447
}
365448
},
366449
_ => {
367-
println!("cannot parse {:?} attribute: {:?}", path, nested);
450+
return Err(syn::Error::new_spanned(
451+
nested.first().unwrap(),
452+
"expected ident or string literal for property name",
453+
))
368454
}
369-
}
455+
};
370456
} else if path.is_ident("args") {
371457
let attrs = PyFunctionAttr::from_meta(nested)?;
372-
spec.extend(attrs.arguments)
458+
args.extend(attrs.arguments)
373459
} else {
374460
new_attrs.push(attr.clone())
375461
}
376462
}
377463
syn::Meta::NameValue(_) => new_attrs.push(attr.clone()),
378464
}
379465
}
466+
380467
attrs.clear();
381468
attrs.extend(new_attrs);
382469

383-
match res {
384-
Some(tp) => Ok((tp, spec)),
385-
None => Ok((FnType::Fn, spec)),
470+
let ty = res.unwrap_or(FnType::Fn);
471+
let python_name = if allow_custom_name {
472+
parse_method_name_attribute(&ty, attrs, property_name)?
473+
} else {
474+
property_name
475+
};
476+
477+
Ok(MethodAttributes {
478+
ty,
479+
args,
480+
python_name,
481+
})
482+
}
483+
484+
fn parse_method_name_attribute(
485+
ty: &FnType,
486+
attrs: &mut Vec<syn::Attribute>,
487+
property_name: Option<syn::Ident>,
488+
) -> syn::Result<Option<syn::Ident>> {
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 | FnType::FnCall | FnType::Getter | FnType::Setter => {
495+
return Err(syn::Error::new_spanned(
496+
name,
497+
"name not allowed with this attribute",
498+
))
499+
}
500+
_ => {}
501+
}
386502
}
503+
504+
// Thanks to check above we can be sure that this generates the right python name
505+
Ok(match ty {
506+
FnType::FnNew => Some(syn::Ident::new("__new__", proc_macro2::Span::call_site())),
507+
FnType::FnCall => Some(syn::Ident::new("__call__", proc_macro2::Span::call_site())),
508+
FnType::Getter | FnType::Setter => property_name,
509+
_ => name,
510+
})
387511
}
388512

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

0 commit comments

Comments
 (0)