Skip to content

Commit

Permalink
Merge pull request #692 from davidhewitt/override-method-names
Browse files Browse the repository at this point in the history
Add #[name = "foo"] attribute to #[pymethods]
  • Loading branch information
kngwyu authored Dec 19, 2019
2 parents 1518767 + b245e71 commit c8cb3ad
Show file tree
Hide file tree
Showing 17 changed files with 504 additions and 302 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## Unreleased

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

## [0.8.4]

### Added
Expand Down
18 changes: 9 additions & 9 deletions pyo3-derive-backend/src/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ pub enum MethodProto {
},
}

impl PartialEq<str> for MethodProto {
fn eq(&self, name: &str) -> bool {
impl MethodProto {
pub fn name(&self) -> &str {
match *self {
MethodProto::Free { name: n, .. } => n == name,
MethodProto::Unary { name: n, .. } => n == name,
MethodProto::Binary { name: n, .. } => n == name,
MethodProto::BinaryS { name: n, .. } => n == name,
MethodProto::Ternary { name: n, .. } => n == name,
MethodProto::TernaryS { name: n, .. } => n == name,
MethodProto::Quaternary { name: n, .. } => n == name,
MethodProto::Free { ref name, .. } => name,
MethodProto::Unary { ref name, .. } => name,
MethodProto::Binary { ref name, .. } => name,
MethodProto::BinaryS { ref name, .. } => name,
MethodProto::Ternary { ref name, .. } => name,
MethodProto::TernaryS { ref name, .. } => name,
MethodProto::Quaternary { ref name, .. } => name,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pyo3-derive-backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
200 changes: 162 additions & 38 deletions pyo3-derive-backend/src/method.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Copyright (c) 2017-present PyO3 Project and Contributors

use crate::pyfunction::Argument;
use crate::pyfunction::PyFunctionAttr;
use crate::pyfunction::{parse_name_attribute, PyFunctionAttr};
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)]
Expand All @@ -20,8 +22,8 @@ pub struct FnArg<'a> {

#[derive(Clone, PartialEq, Debug)]
pub enum FnType {
Getter(Option<String>),
Setter(Option<String>),
Getter,
Setter,
Fn,
FnNew,
FnCall,
Expand All @@ -33,9 +35,15 @@ pub enum FnType {
#[derive(Clone, PartialEq, Debug)]
pub struct FnSpec<'a> {
pub tp: FnType,
// Rust function name
pub name: &'a syn::Ident,
// Wrapped python name. This should not have any leading r#.
// r# can be removed by syn::ext::IdentExt::unraw()
pub python_name: syn::Ident,
pub attrs: Vec<Argument>,
pub args: Vec<FnArg<'a>>,
pub output: syn::Type,
pub doc: syn::LitStr,
}

pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
Expand All @@ -48,11 +56,16 @@ pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
impl<'a> FnSpec<'a> {
/// Parser function signature and function attributes
pub fn parse(
name: &'a syn::Ident,
sig: &'a syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
allow_custom_name: bool,
) -> syn::Result<FnSpec<'a>> {
let (mut fn_type, fn_attrs) = parse_attributes(meth_attrs)?;
let name = &sig.ident;
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();
Expand Down Expand Up @@ -112,11 +125,58 @@ impl<'a> FnSpec<'a> {
fn_type = FnType::PySelf(tp);
}

// "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(meth_attrs, &python_name)?
{
Err(syn::Error::new_spanned(text_signature, error_msg))
} else {
Ok(None)
}
};

let text_signature = match &fn_type {
FnType::Fn | FnType::PySelf(_) | 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",
)?,
FnType::FnCall | FnType::Getter | FnType::Setter => {
parse_erroneous_text_signature("text_signature not allowed with this attribute")?
}
};

let doc = utils::get_doc(&meth_attrs, text_signature, true)?;

Ok(FnSpec {
tp: fn_type,
name,
python_name,
attrs: fn_attrs,
args: arguments,
output: ty,
doc,
})
}

Expand Down Expand Up @@ -279,10 +339,21 @@ pub fn check_arg_ty_and_optional<'a>(
}
}

fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<(FnType, Vec<Argument>)> {
#[derive(Clone, PartialEq, Debug)]
struct MethodAttributes {
ty: FnType,
args: Vec<Argument>,
python_name: Option<syn::Ident>,
}

fn parse_method_attributes(
attrs: &mut Vec<syn::Attribute>,
allow_custom_name: bool,
) -> syn::Result<MethodAttributes> {
let mut new_attrs = Vec::new();
let mut spec = Vec::new();
let mut args = Vec::new();
let mut res: Option<FnType> = None;
let mut property_name = None;

for attr in attrs.iter() {
match attr.parse_meta()? {
Expand All @@ -302,15 +373,21 @@ fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<(FnType, Vec
res = Some(FnType::FnStatic)
} else if name.is_ident("setter") || name.is_ident("getter") {
if let syn::AttrStyle::Inner(_) = attr.style {
panic!("Inner style attribute is not supported for setter and getter");
return Err(syn::Error::new_spanned(
attr,
"Inner style attribute is not supported for setter and getter",
));
}
if res != None {
panic!("setter/getter attribute can not be used mutiple times");
return Err(syn::Error::new_spanned(
attr,
"setter/getter attribute can not be used mutiple times",
));
}
if name.is_ident("setter") {
res = Some(FnType::Setter(None))
res = Some(FnType::Setter)
} else {
res = Some(FnType::Getter(None))
res = Some(FnType::Getter)
}
} else {
new_attrs.push(attr.clone())
Expand All @@ -332,58 +409,105 @@ fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<(FnType, Vec
res = Some(FnType::FnCall)
} else if path.is_ident("setter") || path.is_ident("getter") {
if let syn::AttrStyle::Inner(_) = attr.style {
panic!(
"Inner style attribute is not
supported for setter and getter"
);
return Err(syn::Error::new_spanned(
attr,
"Inner style attribute is not supported for setter and getter",
));
}
if res != None {
panic!("setter/getter attribute can not be used mutiple times");
return Err(syn::Error::new_spanned(
attr,
"setter/getter attribute can not be used mutiple times",
));
}
if nested.len() != 1 {
panic!("setter/getter requires one value");
return Err(syn::Error::new_spanned(
attr,
"setter/getter requires one value",
));
}
match nested.first().unwrap() {
syn::NestedMeta::Meta(syn::Meta::Path(ref w)) => {
if path.is_ident("setter") {
res = Some(FnType::Setter(Some(w.segments[0].ident.to_string())))
} else {
res = Some(FnType::Getter(Some(w.segments[0].ident.to_string())))
}

res = if path.is_ident("setter") {
Some(FnType::Setter)
} else {
Some(FnType::Getter)
};

property_name = match nested.first().unwrap() {
syn::NestedMeta::Meta(syn::Meta::Path(ref w)) if w.segments.len() == 1 => {
Some(w.segments[0].ident.clone())
}
syn::NestedMeta::Lit(ref lit) => match *lit {
syn::Lit::Str(ref s) => {
if path.is_ident("setter") {
res = Some(FnType::Setter(Some(s.value())))
} else {
res = Some(FnType::Getter(Some(s.value())))
}
}
syn::Lit::Str(ref s) => Some(s.parse()?),
_ => {
panic!("setter/getter attribute requires str value");
return Err(syn::Error::new_spanned(
lit,
"setter/getter attribute requires str value",
))
}
},
_ => {
println!("cannot parse {:?} attribute: {:?}", path, nested);
return Err(syn::Error::new_spanned(
nested.first().unwrap(),
"expected ident or string literal for property name",
))
}
}
};
} 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(_) => new_attrs.push(attr.clone()),
}
}

attrs.clear();
attrs.extend(new_attrs);

match res {
Some(tp) => Ok((tp, spec)),
None => Ok((FnType::Fn, spec)),
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<syn::Attribute>,
property_name: Option<syn::Ident>,
) -> syn::Result<Option<syn::Ident>> {
let name = parse_name_attribute(attrs)?;

// Reject some invalid combinations
if let Some(name) = &name {
match ty {
FnType::FnNew | FnType::FnCall | FnType::Getter | FnType::Setter => {
return Err(syn::Error::new_spanned(
name,
"name not allowed with this attribute",
))
}
_ => {}
}
}

// Thanks to check above we can be sure that this generates the right 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<Self> with A<_>
Expand Down
Loading

0 comments on commit c8cb3ad

Please sign in to comment.