Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add #[name = "foo"] attribute to #[pymethods] #692

Merged
merged 5 commits into from
Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
224 changes: 186 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 have been sent through syn::IdentExt::unraw()
davidhewitt marked this conversation as resolved.
Show resolved Hide resolved
// to ensure that any leading r# is removed.
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,64 @@ 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 => {
parse_erroneous_text_signature("text_signature not allowed on __call__")?
}
FnType::Getter => {
parse_erroneous_text_signature("text_signature not allowed on getter")?
}
FnType::Setter => {
parse_erroneous_text_signature("text_signature not allowed on setter")?
}
};

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 +345,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 +379,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 +415,123 @@ 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 => {
return Err(syn::Error::new_spanned(
davidhewitt marked this conversation as resolved.
Show resolved Hide resolved
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
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