macros: support `#[pyo3(name = "...")]`

This commit is contained in:
David Hewitt 2021-04-17 22:22:06 +01:00
parent 4613b3dd7e
commit 4d46abde73
31 changed files with 1032 additions and 702 deletions

View File

@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Add FFI definition `_Py_InitializeMain`. [#1473](https://github.com/PyO3/pyo3/pull/1473) - Add FFI definition `_Py_InitializeMain`. [#1473](https://github.com/PyO3/pyo3/pull/1473)
- Add FFI definitions from `cpython/import.h`.[#1475](https://github.com/PyO3/pyo3/pull/1475) - Add FFI definitions from `cpython/import.h`.[#1475](https://github.com/PyO3/pyo3/pull/1475)
- Add tuple and unit struct support for `#[pyclass]` macro. [#1504](https://github.com/PyO3/pyo3/pull/1504) - Add tuple and unit struct support for `#[pyclass]` macro. [#1504](https://github.com/PyO3/pyo3/pull/1504)
- Add `#[pyo3(name = "...")]` syntax for setting Python names. [#1567](https://github.com/PyO3/pyo3/pull/1567)
- Add FFI definition `PyDateTime_TimeZone_UTC`. [#1572](https://github.com/PyO3/pyo3/pull/1572) - Add FFI definition `PyDateTime_TimeZone_UTC`. [#1572](https://github.com/PyO3/pyo3/pull/1572)
- Add support for `#[pyclass(extends=Exception)]`. [#1591](https://github.com/PyO3/pyo3/pull/1591) - Add support for `#[pyclass(extends=Exception)]`. [#1591](https://github.com/PyO3/pyo3/pull/1591)
@ -40,6 +41,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `PyMappingProtocol::__reversed__` - `PyMappingProtocol::__reversed__`
- `PyNumberProtocol::__complex__` and `PyNumberProtocol::__round__` - `PyNumberProtocol::__complex__` and `PyNumberProtocol::__round__`
- `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__` - `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__`
- Deprecate `#[name = "..."]` attributes in favor of `#[pyo3(name = "...")]`. [#1567](https://github.com/PyO3/pyo3/pull/1567)
### Removed ### Removed
- Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426) - Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426)

View File

@ -475,7 +475,7 @@ pub fn solve<T: Model>(model: &mut T) {
} }
#[pyfunction] #[pyfunction]
#[name = "solve"] #[pyo3(name = "solve")]
pub fn solve_wrapper(model: &mut UserModel) { pub fn solve_wrapper(model: &mut UserModel) {
solve(model); solve(model);
} }

View File

@ -0,0 +1,96 @@
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
Attribute, ExprPath, Ident, LitStr, Result, Token,
};
pub mod kw {
syn::custom_keyword!(annotation);
syn::custom_keyword!(attribute);
syn::custom_keyword!(from_py_with);
syn::custom_keyword!(item);
syn::custom_keyword!(pass_module);
syn::custom_keyword!(name);
syn::custom_keyword!(signature);
syn::custom_keyword!(transparent);
}
#[derive(Clone, Debug, PartialEq)]
pub struct FromPyWithAttribute(pub ExprPath);
impl Parse for FromPyWithAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let _: kw::from_py_with = input.parse()?;
let _: Token![=] = input.parse()?;
let string_literal: LitStr = input.parse()?;
string_literal.parse().map(FromPyWithAttribute)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct NameAttribute(pub Ident);
impl Parse for NameAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let _: kw::name = input.parse()?;
let _: Token![=] = input.parse()?;
let string_literal: LitStr = input.parse()?;
string_literal.parse().map(NameAttribute)
}
}
pub fn get_pyo3_attribute<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> {
if attribute_ident_is(attr, "pyo3") {
attr.parse_args_with(Punctuated::parse_terminated).map(Some)
} else {
Ok(None)
}
}
pub fn attribute_ident_is(attr: &syn::Attribute, name: &str) -> bool {
if let Some(path_segment) = attr.path.segments.last() {
attr.path.segments.len() == 1 && path_segment.ident == name
} else {
false
}
}
/// Takes attributes from an attribute vector.
///
/// For each attribute in `attrs`, `extractor` is called. If `extractor` returns `Ok(true)`, then
/// the attribute will be removed from the vector.
///
/// This is similar to `Vec::retain` except the closure is fallible and the condition is reversed.
/// (In `retain`, returning `true` keeps the element, here it removes it.)
pub fn take_attributes(
attrs: &mut Vec<Attribute>,
mut extractor: impl FnMut(&Attribute) -> Result<bool>,
) -> Result<()> {
*attrs = attrs
.drain(..)
.filter_map(|attr| {
extractor(&attr)
.map(move |attribute_handled| if attribute_handled { None } else { Some(attr) })
.transpose()
})
.collect::<Result<_>>()?;
Ok(())
}
pub fn get_deprecated_name_attribute(attr: &syn::Attribute) -> syn::Result<Option<NameAttribute>> {
match attr.parse_meta() {
Ok(syn::Meta::NameValue(syn::MetaNameValue {
path,
lit: syn::Lit::Str(s),
..
})) if path.is_ident("name") => {
let mut ident: syn::Ident = s.parse()?;
// This span is the whole attribute span, which is nicer for reporting errors.
ident.set_span(attr.span());
Ok(Some(NameAttribute(ident)))
}
_ => Ok(None),
}
}

View File

@ -1,22 +0,0 @@
use syn::spanned::Spanned;
use syn::{ExprPath, Lit, Meta, MetaNameValue, Result};
#[derive(Clone, Debug, PartialEq)]
pub struct FromPyWithAttribute(pub ExprPath);
impl FromPyWithAttribute {
pub fn from_meta(meta: Meta) -> Result<Self> {
let string_literal = match meta {
Meta::NameValue(MetaNameValue {
lit: Lit::Str(string_literal),
..
}) => string_literal,
meta => {
bail_spanned!(meta.span() => "expected a name-value: `pyo3(from_py_with = \"func\")`")
}
};
let expr_path = string_literal.parse::<ExprPath>()?;
Ok(FromPyWithAttribute(expr_path))
}
}

View File

@ -1,9 +1,14 @@
use crate::attrs::FromPyWithAttribute; use crate::attributes::{self, get_pyo3_attribute, FromPyWithAttribute};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::punctuated::Punctuated; use syn::{
use syn::spanned::Spanned; parenthesized,
use syn::{parse_quote, Attribute, DataEnum, DeriveInput, Fields, Ident, Meta, MetaList, Result}; parse::{Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
Attribute, DataEnum, DeriveInput, Fields, Ident, LitStr, Result, Token,
};
/// Describes derivation input of an enum. /// Describes derivation input of an enum.
#[derive(Debug)] #[derive(Debug)]
@ -26,7 +31,7 @@ impl<'a> Enum<'a> {
.variants .variants
.iter() .iter()
.map(|variant| { .map(|variant| {
let attrs = ContainerAttribute::parse_attrs(&variant.attrs)?; let attrs = ContainerOptions::from_attrs(&variant.attrs)?;
let var_ident = &variant.ident; let var_ident = &variant.ident;
Container::new( Container::new(
&variant.fields, &variant.fields,
@ -86,7 +91,7 @@ enum ContainerType<'a> {
/// Struct Container, e.g. `struct Foo { a: String }` /// Struct Container, e.g. `struct Foo { a: String }`
/// ///
/// Variant contains the list of field identifiers and the corresponding extraction call. /// Variant contains the list of field identifiers and the corresponding extraction call.
Struct(Vec<(&'a Ident, FieldAttributes)>), Struct(Vec<(&'a Ident, FieldPyO3Attributes)>),
/// Newtype struct container, e.g. `#[transparent] struct Foo { a: String }` /// Newtype struct container, e.g. `#[transparent] struct Foo { a: String }`
/// ///
/// The field specified by the identifier is extracted directly from the object. /// The field specified by the identifier is extracted directly from the object.
@ -119,20 +124,20 @@ impl<'a> Container<'a> {
fn new( fn new(
fields: &'a Fields, fields: &'a Fields,
path: syn::Path, path: syn::Path,
attrs: Vec<ContainerAttribute>, options: ContainerOptions,
is_enum_variant: bool, is_enum_variant: bool,
) -> Result<Self> { ) -> Result<Self> {
ensure_spanned!( ensure_spanned!(
!fields.is_empty(), !fields.is_empty(),
fields.span() => "cannot derive FromPyObject for empty structs and variants" fields.span() => "cannot derive FromPyObject for empty structs and variants"
); );
let transparent = attrs if options.transparent {
.iter() ensure_spanned!(
.any(|attr| *attr == ContainerAttribute::Transparent); fields.len() == 1,
if transparent { fields.span() => "transparent structs and variants can only have 1 field"
Self::check_transparent_len(fields)?; );
} }
let style = match (fields, transparent) { let style = match (fields, options.transparent) {
(Fields::Unnamed(_), true) => ContainerType::TupleNewtype, (Fields::Unnamed(_), true) => ContainerType::TupleNewtype,
(Fields::Unnamed(unnamed), false) => match unnamed.unnamed.len() { (Fields::Unnamed(unnamed), false) => match unnamed.unnamed.len() {
1 => ContainerType::TupleNewtype, 1 => ContainerType::TupleNewtype,
@ -157,17 +162,17 @@ impl<'a> Container<'a> {
.ident .ident
.as_ref() .as_ref()
.expect("Named fields should have identifiers"); .expect("Named fields should have identifiers");
let attrs = FieldAttributes::parse_attrs(&field.attrs)?; let attrs = FieldPyO3Attributes::from_attrs(&field.attrs)?;
fields.push((ident, attrs)) fields.push((ident, attrs))
} }
ContainerType::Struct(fields) ContainerType::Struct(fields)
} }
(Fields::Unit, _) => unreachable!(), // covered by length check above (Fields::Unit, _) => unreachable!(), // covered by length check above
}; };
let err_name = attrs let err_name = options.annotation.map_or_else(
.iter() || path.segments.last().unwrap().ident.to_string(),
.find_map(|a| a.annotation().map(syn::LitStr::value)) |lit_str| lit_str.value(),
.unwrap_or_else(|| path.segments.last().unwrap().ident.to_string()); );
let v = Container { let v = Container {
path, path,
@ -178,18 +183,6 @@ impl<'a> Container<'a> {
Ok(v) Ok(v)
} }
fn verify_struct_container_attrs(attrs: &'a [ContainerAttribute]) -> Result<()> {
for attr in attrs {
match attr {
ContainerAttribute::Transparent => {}
ContainerAttribute::ErrorAnnotation(annotation) => bail_spanned!(
annotation.span() => "annotation is not supported for structs"
),
}
}
Ok(())
}
/// Build derivation body for a struct. /// Build derivation body for a struct.
fn build(&self) -> TokenStream { fn build(&self) -> TokenStream {
match &self.ty { match &self.ty {
@ -235,7 +228,7 @@ impl<'a> Container<'a> {
) )
} }
fn build_struct(&self, tups: &[(&Ident, FieldAttributes)]) -> TokenStream { fn build_struct(&self, tups: &[(&Ident, FieldPyO3Attributes)]) -> TokenStream {
let self_ty = &self.path; let self_ty = &self.path;
let mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new(); let mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new();
for (ident, attrs) in tups { for (ident, attrs) in tups {
@ -256,67 +249,73 @@ impl<'a> Container<'a> {
} }
quote!(Ok(#self_ty{#fields})) quote!(Ok(#self_ty{#fields}))
} }
}
fn check_transparent_len(fields: &Fields) -> Result<()> { struct ContainerOptions {
ensure_spanned!( transparent: bool,
fields.len() == 1, annotation: Option<syn::LitStr>,
fields.span() => "transparent structs and variants can only have 1 field"
);
Ok(())
}
} }
/// Attributes for deriving FromPyObject scoped on containers. /// Attributes for deriving FromPyObject scoped on containers.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum ContainerAttribute { enum ContainerPyO3Attribute {
/// Treat the Container as a Wrapper, directly extract its fields from the input object. /// Treat the Container as a Wrapper, directly extract its fields from the input object.
Transparent, Transparent(attributes::kw::transparent),
/// Change the name of an enum variant in the generated error message. /// Change the name of an enum variant in the generated error message.
ErrorAnnotation(syn::LitStr), ErrorAnnotation(LitStr),
} }
impl ContainerAttribute { impl Parse for ContainerPyO3Attribute {
/// Convenience method to access `ErrorAnnotation`. fn parse(input: ParseStream) -> Result<Self> {
fn annotation(&self) -> Option<&syn::LitStr> { let lookahead = input.lookahead1();
match self { if lookahead.peek(attributes::kw::transparent) {
ContainerAttribute::ErrorAnnotation(s) => Some(s), let kw: attributes::kw::transparent = input.parse()?;
_ => None, Ok(ContainerPyO3Attribute::Transparent(kw))
} else if lookahead.peek(attributes::kw::annotation) {
let _: attributes::kw::annotation = input.parse()?;
let _: Token![=] = input.parse()?;
input.parse().map(ContainerPyO3Attribute::ErrorAnnotation)
} else {
Err(lookahead.error())
} }
} }
}
/// Parse valid container arguments impl ContainerOptions {
/// fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
/// Fails if any are invalid. let mut options = ContainerOptions {
fn parse_attrs(value: &[Attribute]) -> Result<Vec<Self>> { transparent: false,
get_pyo3_meta_list(value)? annotation: None,
.nested };
.into_iter() for attr in attrs {
.map(|meta| { if let Some(pyo3_attrs) = get_pyo3_attribute(attr)? {
if let syn::NestedMeta::Meta(metaitem) = &meta { for pyo3_attr in pyo3_attrs {
match metaitem { match pyo3_attr {
Meta::Path(p) if p.is_ident("transparent") => { ContainerPyO3Attribute::Transparent(kw) => {
return Ok(ContainerAttribute::Transparent); ensure_spanned!(
!options.transparent,
kw.span() => "`transparent` may only be provided once"
);
options.transparent = true;
} }
Meta::NameValue(nv) if nv.path.is_ident("annotation") => { ContainerPyO3Attribute::ErrorAnnotation(lit_str) => {
if let syn::Lit::Str(s) = &nv.lit { ensure_spanned!(
return Ok(ContainerAttribute::ErrorAnnotation(s.clone())); options.annotation.is_none(),
} else { lit_str.span() => "`annotation` may only be provided once"
bail_spanned!(nv.lit.span() => "expected string literal for annotation"); );
} options.annotation = Some(lit_str);
} }
_ => {} // return Err below
} }
} }
}
bail_spanned!(meta.span() => "unknown `pyo3` container attribute"); }
}) Ok(options)
.collect()
} }
} }
/// Attributes for deriving FromPyObject scoped on fields. /// Attributes for deriving FromPyObject scoped on fields.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct FieldAttributes { struct FieldPyO3Attributes {
getter: FieldGetter, getter: FieldGetter,
from_py_with: Option<FromPyWithAttribute>, from_py_with: Option<FromPyWithAttribute>,
} }
@ -324,121 +323,96 @@ struct FieldAttributes {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum FieldGetter { enum FieldGetter {
GetItem(Option<syn::Lit>), GetItem(Option<syn::Lit>),
GetAttr(Option<syn::LitStr>), GetAttr(Option<LitStr>),
} }
impl FieldAttributes { enum FieldPyO3Attribute {
Getter(FieldGetter),
FromPyWith(FromPyWithAttribute),
}
impl Parse for FieldPyO3Attribute {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::attribute) {
let _: attributes::kw::attribute = input.parse()?;
if input.peek(syn::token::Paren) {
let content;
let _ = parenthesized!(content in input);
let attr_name: LitStr = content.parse()?;
if !content.is_empty() {
return Err(content.error(
"expected at most one argument: `attribute` or `attribute(\"name\")`",
));
}
ensure_spanned!(
!attr_name.value().is_empty(),
attr_name.span() => "attribute name cannot be empty"
);
Ok(FieldPyO3Attribute::Getter(FieldGetter::GetAttr(Some(
attr_name,
))))
} else {
Ok(FieldPyO3Attribute::Getter(FieldGetter::GetAttr(None)))
}
} else if lookahead.peek(attributes::kw::item) {
let _: attributes::kw::item = input.parse()?;
if input.peek(syn::token::Paren) {
let content;
let _ = parenthesized!(content in input);
let key = content.parse()?;
if !content.is_empty() {
return Err(
content.error("expected at most one argument: `item` or `item(key)`")
);
}
Ok(FieldPyO3Attribute::Getter(FieldGetter::GetItem(Some(key))))
} else {
Ok(FieldPyO3Attribute::Getter(FieldGetter::GetItem(None)))
}
} else if lookahead.peek(attributes::kw::from_py_with) {
input.parse().map(FieldPyO3Attribute::FromPyWith)
} else {
Err(lookahead.error())
}
}
}
impl FieldPyO3Attributes {
/// Extract the field attributes. /// Extract the field attributes.
/// ///
fn parse_attrs(attrs: &[Attribute]) -> Result<Self> { fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut getter = None; let mut getter = None;
let mut from_py_with = None; let mut from_py_with = None;
let list = get_pyo3_meta_list(attrs)?; for attr in attrs {
if let Some(pyo3_attrs) = get_pyo3_attribute(attr)? {
for meta_item in list.nested { for pyo3_attr in pyo3_attrs {
let meta = match meta_item { match pyo3_attr {
syn::NestedMeta::Meta(meta) => meta, FieldPyO3Attribute::Getter(field_getter) => {
syn::NestedMeta::Lit(lit) => bail_spanned!( ensure_spanned!(
lit.span() => getter.is_none(),
"expected `attribute`, `item` or `from_py_with`, got a literal" attr.span() => "only one of `attribute` or `item` can be provided"
), );
}; getter = Some(field_getter)
let path = meta.path(); }
FieldPyO3Attribute::FromPyWith(from_py_with_attr) => {
if path.is_ident("attribute") { ensure_spanned!(
ensure_spanned!( from_py_with.is_none(),
getter.is_none(), attr.span() => "`from_py_with` may only be provided once"
meta.span() => "only one of `attribute` or `item` can be provided" );
); from_py_with = Some(from_py_with_attr);
getter = Some(FieldGetter::GetAttr(Self::attribute_arg(meta)?)) }
} else if path.is_ident("item") { }
ensure_spanned!( }
getter.is_none(), }
meta.span() => "only one of `attribute` or `item` can be provided"
);
getter = Some(FieldGetter::GetItem(Self::item_arg(meta)?))
} else if path.is_ident("from_py_with") {
from_py_with = Some(Self::from_py_with_arg(meta)?)
} else {
bail_spanned!(meta.span() => "expected `attribute`, `item` or `from_py_with`")
};
} }
Ok(FieldAttributes { Ok(FieldPyO3Attributes {
getter: getter.unwrap_or(FieldGetter::GetAttr(None)), getter: getter.unwrap_or(FieldGetter::GetAttr(None)),
from_py_with, from_py_with,
}) })
} }
fn attribute_arg(meta: Meta) -> syn::Result<Option<syn::LitStr>> {
let mut arg_list = match meta {
Meta::List(list) => list,
Meta::Path(_) => return Ok(None),
Meta::NameValue(nv) => bail_spanned!(
nv.span() =>
"expected a string literal or no argument: `pyo3(attribute(\"name\")` or \
`pyo3(attribute)`"
),
};
if arg_list.nested.len() == 1 {
let arg = arg_list.nested.pop().unwrap().into_value();
if let syn::NestedMeta::Lit(syn::Lit::Str(litstr)) = arg {
ensure_spanned!(
!litstr.value().is_empty(),
litstr.span() => "attribute name cannot be empty"
);
return Ok(Some(litstr));
}
}
bail_spanned!(arg_list.span() => "expected a single string literal argument");
}
fn item_arg(meta: Meta) -> syn::Result<Option<syn::Lit>> {
let mut arg_list = match meta {
Meta::List(list) => list,
Meta::Path(_) => return Ok(None),
Meta::NameValue(nv) => bail_spanned!(
nv.span() => "expected a literal or no argument: `pyo3(item(key)` or `pyo3(item)`"
),
};
if arg_list.nested.len() == 1 {
let arg = arg_list.nested.pop().unwrap().into_value();
if let syn::NestedMeta::Lit(lit) = arg {
return Ok(Some(lit));
}
}
bail_spanned!(arg_list.span() => "expected a single literal argument");
}
fn from_py_with_arg(meta: Meta) -> syn::Result<FromPyWithAttribute> {
FromPyWithAttribute::from_meta(meta)
}
}
/// Extract pyo3 metalist, flattens multiple lists into a single one.
fn get_pyo3_meta_list(attrs: &[Attribute]) -> Result<MetaList> {
let mut list: Punctuated<syn::NestedMeta, syn::Token![,]> = Punctuated::new();
for value in attrs {
match value.parse_meta()? {
Meta::List(ml) if value.path.is_ident("pyo3") => {
for meta in ml.nested {
list.push(meta);
}
}
_ => continue,
}
}
Ok(MetaList {
path: parse_quote!(pyo3),
paren_token: syn::token::Paren::default(),
nested: list,
})
} }
fn verify_and_get_lifetime(generics: &syn::Generics) -> Result<Option<&syn::LifetimeDef>> { fn verify_and_get_lifetime(generics: &syn::Generics) -> Result<Option<&syn::LifetimeDef>> {
@ -481,10 +455,12 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
en.build() en.build()
} }
syn::Data::Struct(st) => { syn::Data::Struct(st) => {
let attrs = ContainerAttribute::parse_attrs(&tokens.attrs)?; let options = ContainerOptions::from_attrs(&tokens.attrs)?;
Container::verify_struct_container_attrs(&attrs)?; if let Some(lit_str) = &options.annotation {
bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs");
}
let ident = &tokens.ident; let ident = &tokens.ident;
let st = Container::new(&st.fields, parse_quote!(#ident), attrs, false)?; let st = Container::new(&st.fields, parse_quote!(#ident), options, false)?;
st.build() st.build()
} }
syn::Data::Union(_) => bail_spanned!( syn::Data::Union(_) => bail_spanned!(

View File

@ -1,34 +1,99 @@
use crate::pyfunction::parse_name_attribute; use crate::attributes::{
use syn::ext::IdentExt; self, attribute_ident_is, get_deprecated_name_attribute, get_pyo3_attribute, take_attributes,
NameAttribute,
};
use crate::utils;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
spanned::Spanned,
Result,
};
#[derive(Clone, PartialEq, Debug)]
pub struct ConstSpec { pub struct ConstSpec {
pub is_class_attr: bool, pub rust_ident: syn::Ident,
pub python_name: syn::Ident, pub attributes: ConstAttributes,
} }
impl ConstSpec { impl ConstSpec {
// For now, the only valid attribute is `#[classattr]`. /// Null-terminated Python name
pub fn parse(name: &syn::Ident, attrs: &mut Vec<syn::Attribute>) -> syn::Result<ConstSpec> { pub fn python_name_with_deprecation(&self) -> TokenStream {
let mut new_attrs = Vec::new(); if let Some(name) = &self.attributes.name {
let mut is_class_attr = false; let deprecation =
utils::name_deprecation_token(name.0.span(), self.attributes.name_is_deprecated);
for attr in attrs.iter() { let name = format!("{}\0", name.0);
if let syn::Meta::Path(name) = attr.parse_meta()? { quote!({#deprecation #name})
if name.is_ident("classattr") { } else {
is_class_attr = true; let name = format!("{}\0", self.rust_ident.unraw().to_string());
continue; quote!(#name)
}
}
new_attrs.push(attr.clone());
} }
}
attrs.clear(); }
attrs.extend(new_attrs);
pub struct ConstAttributes {
Ok(ConstSpec { pub is_class_attr: bool,
is_class_attr, pub name: Option<NameAttribute>,
python_name: parse_name_attribute(attrs)?.unwrap_or_else(|| name.unraw()), pub name_is_deprecated: bool,
}) }
pub enum PyO3ConstAttribute {
Name(NameAttribute),
}
impl Parse for PyO3ConstAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::name) {
input.parse().map(PyO3ConstAttribute::Name)
} else {
Err(lookahead.error())
}
}
}
impl ConstAttributes {
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
let mut attributes = ConstAttributes {
is_class_attr: false,
name: None,
name_is_deprecated: false,
};
take_attributes(attrs, |attr| {
if attribute_ident_is(attr, "classattr") {
ensure_spanned!(
attr.tokens.is_empty(),
attr.span() => "`#[classattr]` does not take any arguments"
);
attributes.is_class_attr = true;
Ok(true)
} else if let Some(pyo3_attributes) = get_pyo3_attribute(attr)? {
for pyo3_attr in pyo3_attributes {
match pyo3_attr {
PyO3ConstAttribute::Name(name) => attributes.set_name(name)?,
}
}
Ok(true)
} else if let Some(name) = get_deprecated_name_attribute(attr)? {
attributes.set_name(name)?;
attributes.name_is_deprecated = true;
Ok(true)
} else {
Ok(false)
}
})?;
Ok(attributes)
}
fn set_name(&mut self, name: NameAttribute) -> Result<()> {
ensure_spanned!(
self.name.is_none(),
name.0.span() => "`name` may only be specified once"
);
self.name = Some(name);
Ok(())
} }
} }

View File

@ -8,7 +8,7 @@
#[macro_use] #[macro_use]
mod utils; mod utils;
mod attrs; mod attributes;
mod defs; mod defs;
mod from_pyobject; mod from_pyobject;
mod konst; mod konst;
@ -22,9 +22,9 @@ mod pymethod;
mod pyproto; mod pyproto;
pub use from_pyobject::build_derive_from_pyobject; pub use from_pyobject::build_derive_from_pyobject;
pub use module::{add_fn_to_module, process_functions_in_module, py_init}; pub use module::{process_functions_in_module, py_init};
pub use pyclass::{build_py_class, PyClassArgs}; pub use pyclass::{build_py_class, PyClassArgs};
pub use pyfunction::{build_py_function, PyFunctionAttr}; pub use pyfunction::{build_py_function, PyFunctionOptions};
pub use pyimpl::{build_py_methods, PyClassMethodsType}; pub use pyimpl::{build_py_methods, PyClassMethodsType};
pub use pyproto::build_py_proto; pub use pyproto::build_py_proto;
pub use utils::get_doc; pub use utils::get_doc;

View File

@ -1,7 +1,8 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::pyfunction::Argument; use crate::pyfunction::Argument;
use crate::pyfunction::{parse_name_attribute, PyFunctionArgAttrs, PyFunctionAttr}; use crate::pyfunction::PyFunctionOptions;
use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature};
use crate::utils; use crate::utils;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::ToTokens; use quote::ToTokens;
@ -17,7 +18,7 @@ pub struct FnArg<'a> {
pub ty: &'a syn::Type, pub ty: &'a syn::Type,
pub optional: Option<&'a syn::Type>, pub optional: Option<&'a syn::Type>,
pub py: bool, pub py: bool,
pub attrs: PyFunctionArgAttrs, pub attrs: PyFunctionArgPyO3Attributes,
} }
impl<'a> FnArg<'a> { impl<'a> FnArg<'a> {
@ -32,7 +33,7 @@ impl<'a> FnArg<'a> {
bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR); bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR);
} }
let arg_attrs = PyFunctionArgAttrs::from_attrs(&mut cap.attrs)?; let arg_attrs = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?;
let (ident, by_ref, mutability) = match *cap.pat { let (ident, by_ref, mutability) = match *cap.pat {
syn::Pat::Ident(syn::PatIdent { syn::Pat::Ident(syn::PatIdent {
ref ident, ref ident,
@ -133,6 +134,7 @@ pub struct FnSpec<'a> {
pub args: Vec<FnArg<'a>>, pub args: Vec<FnArg<'a>>,
pub output: syn::Type, pub output: syn::Type,
pub doc: syn::LitStr, pub doc: syn::LitStr,
pub name_is_deprecated: bool,
} }
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type { pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
@ -161,13 +163,29 @@ impl<'a> FnSpec<'a> {
pub fn parse( pub fn parse(
sig: &'a mut syn::Signature, sig: &'a mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>, meth_attrs: &mut Vec<syn::Attribute>,
allow_custom_name: bool, options: PyFunctionOptions,
) -> syn::Result<FnSpec<'a>> { ) -> syn::Result<FnSpec<'a>> {
let MethodAttributes { let MethodAttributes {
ty: fn_type_attr, ty: fn_type_attr,
args: fn_attrs, args: fn_attrs,
mut python_name, mut python_name,
} = parse_method_attributes(meth_attrs, allow_custom_name)?; } = parse_method_attributes(meth_attrs, options.name.map(|name| name.0))?;
match fn_type_attr {
Some(MethodTypeAttribute::New) => {
if let Some(name) = &python_name {
bail_spanned!(name.span() => "`name` not allowed with `#[new]`");
}
python_name = Some(syn::Ident::new("__new__", proc_macro2::Span::call_site()))
}
Some(MethodTypeAttribute::Call) => {
if let Some(name) = &python_name {
bail_spanned!(name.span() => "`name` not allowed with `#[call]`");
}
python_name = Some(syn::Ident::new("__call__", proc_macro2::Span::call_site()))
}
_ => {}
}
let (fn_type, skip_first_arg) = Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?; let (fn_type, skip_first_arg) = Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
@ -199,9 +217,17 @@ impl<'a> FnSpec<'a> {
args: arguments, args: arguments,
output: ty, output: ty,
doc, doc,
name_is_deprecated: options.name_is_deprecated,
}) })
} }
pub fn python_name_with_deprecation(&self) -> TokenStream {
let deprecation =
utils::name_deprecation_token(self.python_name.span(), self.name_is_deprecated);
let name = format!("{}\0", self.python_name);
quote!({#deprecation #name})
}
fn parse_text_signature( fn parse_text_signature(
meth_attrs: &mut Vec<syn::Attribute>, meth_attrs: &mut Vec<syn::Attribute>,
fn_type: &FnType, fn_type: &FnType,
@ -362,12 +388,11 @@ struct MethodAttributes {
fn parse_method_attributes( fn parse_method_attributes(
attrs: &mut Vec<syn::Attribute>, attrs: &mut Vec<syn::Attribute>,
allow_custom_name: bool, mut python_name: Option<syn::Ident>,
) -> syn::Result<MethodAttributes> { ) -> syn::Result<MethodAttributes> {
let mut new_attrs = Vec::new(); let mut new_attrs = Vec::new();
let mut args = Vec::new(); let mut args = Vec::new();
let mut ty: Option<MethodTypeAttribute> = None; let mut ty: Option<MethodTypeAttribute> = None;
let mut property_name = None;
macro_rules! set_ty { macro_rules! set_ty {
($new_ty:expr, $ident:expr) => { ($new_ty:expr, $ident:expr) => {
@ -434,7 +459,12 @@ fn parse_method_attributes(
set_ty!(MethodTypeAttribute::Getter, path); set_ty!(MethodTypeAttribute::Getter, path);
}; };
property_name = match nested.pop().unwrap().into_value() { ensure_spanned!(
python_name.is_none(),
python_name.span() => "`name` may only be specified once"
);
python_name = match nested.pop().unwrap().into_value() {
syn::NestedMeta::Meta(syn::Meta::Path(w)) if w.segments.len() == 1 => { syn::NestedMeta::Meta(syn::Meta::Path(w)) if w.segments.len() == 1 => {
Some(w.segments[0].ident.clone()) Some(w.segments[0].ident.clone())
} }
@ -455,7 +485,7 @@ fn parse_method_attributes(
} }
}; };
} else if path.is_ident("args") { } else if path.is_ident("args") {
let attrs = PyFunctionAttr::from_meta(&nested)?; let attrs = PyFunctionSignature::from_meta(&nested)?;
args.extend(attrs.arguments) args.extend(attrs.arguments)
} else { } else {
new_attrs.push(attr) new_attrs.push(attr)
@ -467,21 +497,6 @@ fn parse_method_attributes(
*attrs = new_attrs; *attrs = new_attrs;
let python_name = if allow_custom_name {
match parse_method_name_attribute(ty.as_ref(), attrs)? {
Some(python_name) if property_name.is_some() => {
return Err(syn::Error::new_spanned(
python_name,
"name cannot be specified twice",
));
}
Some(python_name) => Some(python_name),
None => property_name,
}
} else {
property_name
};
Ok(MethodAttributes { Ok(MethodAttributes {
ty, ty,
args, args,
@ -489,26 +504,4 @@ fn parse_method_attributes(
}) })
} }
fn parse_method_name_attribute(
ty: Option<&MethodTypeAttribute>,
attrs: &mut Vec<syn::Attribute>,
) -> syn::Result<Option<syn::Ident>> {
use MethodTypeAttribute::*;
let name = parse_name_attribute(attrs)?;
// Reject some invalid combinations
if let (Some(name), Some(ty)) = (&name, ty) {
if let New | Call = ty {
bail_spanned!(name.span() => "name not allowed with this method type");
}
}
// Thanks to check above we can be sure that this generates the right python name
Ok(match ty {
Some(New) => Some(syn::Ident::new("__new__", proc_macro2::Span::call_site())),
Some(Call) => Some(syn::Ident::new("__call__", proc_macro2::Span::call_site())),
_ => name,
})
}
const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments"; const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments";

View File

@ -1,13 +1,11 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
//! Code generation for the function that initializes a python module and adds classes and function. //! Code generation for the function that initializes a python module and adds classes and function.
use crate::method::{self, FnArg}; use crate::attributes::{attribute_ident_is, take_attributes, NameAttribute};
use crate::pyfunction::PyFunctionAttr; use crate::pyfunction::{impl_wrap_pyfunction, PyFunctionOptions};
use crate::pymethod::{check_generic, get_arg_names, impl_arg_params};
use crate::utils;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote}; use quote::quote;
use syn::{spanned::Spanned, Ident, Result}; use syn::{parse::Parse, spanned::Spanned, token::Comma, Ident, Path};
/// Generates the function that is called by the python interpreter to initialize the native /// Generates the function that is called by the python interpreter to initialize the native
/// module /// module
@ -35,15 +33,13 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
for stmt in func.block.stmts.iter_mut() { for stmt in func.block.stmts.iter_mut() {
if let syn::Stmt::Item(syn::Item::Fn(func)) = stmt { if let syn::Stmt::Item(syn::Item::Fn(func)) = stmt {
if let Some((module_name, python_name, pyfn_attrs)) = if let Some(pyfn_args) = get_pyfn_attr(&mut func.attrs)? {
extract_pyfn_attrs(&mut func.attrs)? let module_name = pyfn_args.modname;
{ let (ident, wrapped_function) = impl_wrap_pyfunction(func, pyfn_args.options)?;
let function_to_python = add_fn_to_module(func, python_name, pyfn_attrs)?;
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
let item: syn::ItemFn = syn::parse_quote! { let item: syn::ItemFn = syn::parse_quote! {
fn block_wrapper() { fn block_wrapper() {
#function_to_python #wrapped_function
#module_name.add_function(#function_wrapper_ident(#module_name)?)?; #module_name.add_function(#ident(#module_name)?)?;
} }
}; };
stmts.extend(item.block.stmts.into_iter()); stmts.extend(item.block.stmts.into_iter());
@ -56,190 +52,49 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
Ok(()) Ok(())
} }
pub struct PyFnArgs {
modname: Path,
options: PyFunctionOptions,
}
impl Parse for PyFnArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let modname = input.parse()?;
let _: Comma = input.parse()?;
let fnname_literal: syn::LitStr = input.parse()?;
let fnname = fnname_literal.parse()?;
if input.is_empty() {
let mut options = PyFunctionOptions::default();
options.set_name(NameAttribute(fnname))?;
return Ok(Self { modname, options });
}
let _: Comma = input.parse()?;
let mut options: PyFunctionOptions = input.parse()?;
options.set_name(NameAttribute(fnname))?;
Ok(Self { modname, options })
}
}
/// Extracts the data from the #[pyfn(...)] attribute of a function /// Extracts the data from the #[pyfn(...)] attribute of a function
fn extract_pyfn_attrs( fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs>> {
attrs: &mut Vec<syn::Attribute>, let mut pyfn_args: Option<PyFnArgs> = None;
) -> syn::Result<Option<(syn::Path, Ident, PyFunctionAttr)>> {
let mut new_attrs = Vec::new();
let mut fnname = None;
let mut modname = None;
let mut fn_attrs = PyFunctionAttr::default();
for attr in attrs.drain(..) { take_attributes(attrs, |attr| {
match attr.parse_meta() { if attribute_ident_is(attr, "pyfn") {
Ok(syn::Meta::List(list)) if list.path.is_ident("pyfn") => { ensure_spanned!(
let meta: Vec<_> = list.nested.iter().cloned().collect(); pyfn_args.is_none(),
if meta.len() >= 2 { attr.span() => "`#[pyfn] may only be specified once"
// read module name );
match &meta[0] { pyfn_args = Some(attr.parse_args()?);
syn::NestedMeta::Meta(syn::Meta::Path(path)) => { Ok(true)
modname = Some(path.clone()) } else {
} Ok(false)
_ => bail_spanned!(
meta[0].span() => "the first parameter of pyfn must be a MetaItem"
),
}
// read Python function name
match &meta[1] {
syn::NestedMeta::Lit(syn::Lit::Str(lits)) => {
fnname = Some(syn::Ident::new(&lits.value(), lits.span()));
}
_ => bail_spanned!(
meta[1].span() => "the second parameter of pyfn must be a Literal"
),
}
// Read additional arguments
if list.nested.len() >= 3 {
fn_attrs = PyFunctionAttr::from_meta(&meta[2..meta.len()])?;
}
} else {
bail_spanned!(
attr.span() => format!("can not parse 'pyfn' params {:?}", attr)
);
}
}
_ => new_attrs.push(attr),
} }
})?;
if let Some(pyfn_args) = &mut pyfn_args {
pyfn_args.options.take_pyo3_attributes(attrs)?;
} }
*attrs = new_attrs; Ok(pyfn_args)
match (modname, fnname) {
(Some(modname), Some(fnname)) => Ok(Some((modname, fnname, fn_attrs))),
_ => Ok(None),
}
}
/// Coordinates the naming of a the add-function-to-python-module function
fn function_wrapper_ident(name: &Ident) -> Ident {
// Make sure this ident matches the one of wrap_pyfunction
format_ident!("__pyo3_get_function_{}", name)
}
/// Generates python wrapper over a function that allows adding it to a python module as a python
/// function
pub fn add_fn_to_module(
func: &mut syn::ItemFn,
python_name: Ident,
pyfn_attrs: PyFunctionAttr,
) -> syn::Result<TokenStream> {
check_generic(&func.sig)?;
let mut arguments = func
.sig
.inputs
.iter_mut()
.map(FnArg::parse)
.collect::<syn::Result<Vec<_>>>()?;
if pyfn_attrs.pass_module {
const PASS_MODULE_ERR: &str = "expected &PyModule as first argument with `pass_module`";
ensure_spanned!(
!arguments.is_empty(),
func.span() => PASS_MODULE_ERR
);
let arg = arguments.remove(0);
ensure_spanned!(
type_is_pymodule(arg.ty),
arg.ty.span() => PASS_MODULE_ERR
);
}
let ty = method::get_return_info(&func.sig.output);
let text_signature = utils::parse_text_signature_attrs(&mut func.attrs, &python_name)?;
let doc = utils::get_doc(&func.attrs, text_signature, true)?;
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
let spec = method::FnSpec {
tp: method::FnType::FnStatic,
name: &function_wrapper_ident,
python_name,
attrs: pyfn_attrs.arguments,
args: arguments,
output: ty,
doc,
};
let doc = &spec.doc;
let python_name = &spec.python_name;
let name = &func.sig.ident;
let wrapper_ident = format_ident!("__pyo3_raw_{}", name);
let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, pyfn_attrs.pass_module)?;
Ok(quote! {
#wrapper
pub(crate) fn #function_wrapper_ident<'a>(
args: impl Into<pyo3::derive_utils::PyFunctionArguments<'a>>
) -> pyo3::PyResult<&'a pyo3::types::PyCFunction> {
let name = concat!(stringify!(#python_name), "\0");
pyo3::types::PyCFunction::internal_new(
pyo3::class::methods::PyMethodDef::cfunction_with_keywords(
name,
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper_ident),
#doc,
),
args.into(),
)
}
})
}
fn type_is_pymodule(ty: &syn::Type) -> bool {
if let syn::Type::Reference(tyref) = ty {
if let syn::Type::Path(typath) = tyref.elem.as_ref() {
if typath
.path
.segments
.last()
.map(|seg| seg.ident == "PyModule")
.unwrap_or(false)
{
return true;
}
}
}
false
}
/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
fn function_c_wrapper(
name: &Ident,
wrapper_ident: &Ident,
spec: &method::FnSpec<'_>,
pass_module: bool,
) -> Result<TokenStream> {
let names: Vec<Ident> = get_arg_names(&spec);
let cb;
let slf_module;
if pass_module {
cb = quote! {
pyo3::callback::convert(_py, #name(_slf, #(#names),*))
};
slf_module = Some(quote! {
let _slf = _py.from_borrowed_ptr::<pyo3::types::PyModule>(_slf);
});
} else {
cb = quote! {
pyo3::callback::convert(_py, #name(#(#names),*))
};
slf_module = None;
};
let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, None, cb, &py)?;
Ok(quote! {
unsafe extern "C" fn #wrapper_ident(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
pyo3::callback::handle_panic(|#py| {
#slf_module
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body
})
}
})
} }

View File

@ -499,21 +499,21 @@ fn impl_descriptors(
.flat_map(|(field, fns)| { .flat_map(|(field, fns)| {
fns.iter() fns.iter()
.map(|desc| { .map(|desc| {
if let Some(name) = field.ident.as_ref().map(|ident| ident.unraw()) { let doc = utils::get_doc(&field.attrs, None, true)
let doc = utils::get_doc(&field.attrs, None, true) .unwrap_or_else(|_| syn::LitStr::new("", Span::call_site()));
.unwrap_or_else(|_| syn::LitStr::new(&name.to_string(), name.span())); let property_type = PropertyType::Descriptor(
let property_type = PropertyType::Descriptor(&field); field.ident.as_ref().ok_or_else(
match desc { || err_spanned!(field.span() => "`#[pyo3(get, set)]` is not supported on tuple struct fields")
FnType::Getter(self_ty) => { )?
impl_py_getter_def(cls, property_type, self_ty, &name, &doc) );
} match desc {
FnType::Setter(self_ty) => { FnType::Getter(self_ty) => {
impl_py_setter_def(cls, property_type, self_ty, &name, &doc) impl_py_getter_def(cls, property_type, self_ty, &doc)
}
_ => unreachable!(),
} }
} else { FnType::Setter(self_ty) => {
bail_spanned!(field.span() => "get/set are not supported on tuple struct field"); impl_py_setter_def(cls, property_type, self_ty, &doc)
}
_ => unreachable!(),
} }
}) })
.collect::<Vec<syn::Result<TokenStream>>>() .collect::<Vec<syn::Result<TokenStream>>>()

View File

@ -1,13 +1,22 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::attrs::FromPyWithAttribute; use crate::{
use crate::module::add_fn_to_module; attributes::{
use proc_macro2::TokenStream; self, get_deprecated_name_attribute, get_pyo3_attribute, take_attributes,
use syn::ext::IdentExt; FromPyWithAttribute, NameAttribute,
use syn::parse::ParseBuffer; },
method::{self, FnArg, FnSpec},
pymethod::{check_generic, get_arg_names, impl_arg_params},
utils,
};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::spanned::Spanned; use syn::{ext::IdentExt, spanned::Spanned, Ident, NestedMeta, Path, Result};
use syn::{NestedMeta, Path}; use syn::{
parse::{Parse, ParseBuffer, ParseStream},
token::Comma,
};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Argument { pub enum Argument {
@ -20,29 +29,69 @@ pub enum Argument {
/// The attributes of the pyfunction macro /// The attributes of the pyfunction macro
#[derive(Default)] #[derive(Default)]
pub struct PyFunctionAttr { pub struct PyFunctionSignature {
pub arguments: Vec<Argument>, pub arguments: Vec<Argument>,
has_kw: bool, has_kw: bool,
has_varargs: bool, has_varargs: bool,
has_kwargs: bool, has_kwargs: bool,
pub pass_module: bool,
} }
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct PyFunctionArgAttrs { pub struct PyFunctionArgPyO3Attributes {
pub from_py_with: Option<FromPyWithAttribute>, pub from_py_with: Option<FromPyWithAttribute>,
} }
impl syn::parse::Parse for PyFunctionAttr { enum PyFunctionArgPyO3Attribute {
FromPyWith(FromPyWithAttribute),
}
impl Parse for PyFunctionArgPyO3Attribute {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::from_py_with) {
input.parse().map(PyFunctionArgPyO3Attribute::FromPyWith)
} else {
Err(lookahead.error())
}
}
}
impl PyFunctionArgPyO3Attributes {
/// Parses #[pyo3(from_python_with = "func")]
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
let mut attributes = PyFunctionArgPyO3Attributes { from_py_with: None };
take_attributes(attrs, |attr| {
if let Some(pyo3_attrs) = get_pyo3_attribute(attr)? {
for attr in pyo3_attrs {
match attr {
PyFunctionArgPyO3Attribute::FromPyWith(from_py_with) => {
ensure_spanned!(
attributes.from_py_with.is_none(),
from_py_with.0.span() => "`from_py_with` may only be specified once per argument"
);
attributes.from_py_with = Some(from_py_with);
}
}
}
Ok(true)
} else {
Ok(false)
}
})?;
Ok(attributes)
}
}
impl syn::parse::Parse for PyFunctionSignature {
fn parse(input: &ParseBuffer) -> syn::Result<Self> { fn parse(input: &ParseBuffer) -> syn::Result<Self> {
let attr = Punctuated::<NestedMeta, syn::Token![,]>::parse_terminated(input)?; let attr = Punctuated::<NestedMeta, syn::Token![,]>::parse_terminated(input)?;
Self::from_meta(&attr) Self::from_meta(&attr)
} }
} }
impl PyFunctionAttr { impl PyFunctionSignature {
pub fn from_meta<'a>(iter: impl IntoIterator<Item = &'a NestedMeta>) -> syn::Result<Self> { pub fn from_meta<'a>(iter: impl IntoIterator<Item = &'a NestedMeta>) -> syn::Result<Self> {
let mut slf = PyFunctionAttr::default(); let mut slf = PyFunctionSignature::default();
for item in iter { for item in iter {
slf.add_item(item)? slf.add_item(item)?
@ -52,9 +101,6 @@ impl PyFunctionAttr {
pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> { pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> {
match item { match item {
NestedMeta::Meta(syn::Meta::Path(ident)) if ident.is_ident("pass_module") => {
self.pass_module = true;
}
NestedMeta::Meta(syn::Meta::Path(ident)) => self.add_work(item, ident)?, NestedMeta::Meta(syn::Meta::Path(ident)) => self.add_work(item, ident)?,
NestedMeta::Meta(syn::Meta::NameValue(nv)) => { NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
self.add_name_value(item, nv)?; self.add_name_value(item, nv)?;
@ -159,92 +205,286 @@ impl PyFunctionAttr {
} }
} }
pub fn parse_name_attribute(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<syn::Ident>> { #[derive(Default)]
let mut name_attrs = Vec::new(); pub struct PyFunctionOptions {
pub pass_module: bool,
// Using retain will extract all name attributes from the attribute list pub name: Option<NameAttribute>,
attrs.retain(|attr| match attr.parse_meta() { pub name_is_deprecated: bool,
Ok(syn::Meta::NameValue(nv)) if nv.path.is_ident("name") => { pub signature: Option<PyFunctionSignature>,
name_attrs.push((nv.lit, attr.span()));
false
}
_ => true,
});
match name_attrs.as_slice() {
[] => Ok(None),
[(syn::Lit::Str(s), span)] => {
let mut ident: syn::Ident = s.parse()?;
// This span is the whole attribute span, which is nicer for reporting errors.
ident.set_span(*span);
Ok(Some(ident))
}
[(_, span)] => bail_spanned!(*span => "expected string literal for #[name] argument"),
slice => bail_spanned!(
slice[1].1 => "#[name] can not be specified multiple times"
),
}
} }
pub fn build_py_function(ast: &mut syn::ItemFn, args: PyFunctionAttr) -> syn::Result<TokenStream> { impl Parse for PyFunctionOptions {
let python_name = fn parse(input: ParseStream) -> Result<Self> {
parse_name_attribute(&mut ast.attrs)?.unwrap_or_else(|| ast.sig.ident.unraw()); let mut options = PyFunctionOptions {
add_fn_to_module(ast, python_name, args) pass_module: false,
} name: None,
name_is_deprecated: false,
signature: None,
};
fn extract_pyo3_metas(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Vec<syn::NestedMeta>> { while !input.is_empty() {
let mut new_attrs = Vec::new(); let lookahead = input.lookahead1();
let mut metas = Vec::new(); if lookahead.peek(attributes::kw::name)
|| lookahead.peek(attributes::kw::pass_module)
for attr in attrs.drain(..) { || lookahead.peek(attributes::kw::signature)
if let syn::Meta::List(meta_list) = attr.parse_meta()? { {
if meta_list.path.is_ident("pyo3") { options.add_attributes(std::iter::once(input.parse()?))?;
for meta in meta_list.nested { if !input.is_empty() {
metas.push(meta); let _: Comma = input.parse()?;
} }
} else { } else {
new_attrs.push(attr) // If not recognised attribute, this is "legacy" pyfunction syntax #[pyfunction(a, b)]
//
// TODO deprecate in favour of #[pyfunction(signature = (a, b), name = "foo")]
options.signature = Some(input.parse()?);
break;
} }
} }
}
*attrs = new_attrs;
Ok(metas) Ok(options)
}
} }
impl PyFunctionArgAttrs { pub enum PyFunctionOption {
/// Parses #[pyo3(from_python_with = "func")] Name(NameAttribute),
PassModule(attributes::kw::pass_module),
Signature(PyFunctionSignature),
}
impl Parse for PyFunctionOption {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::name) {
input.parse().map(PyFunctionOption::Name)
} else if lookahead.peek(attributes::kw::pass_module) {
input.parse().map(PyFunctionOption::PassModule)
} else if lookahead.peek(attributes::kw::signature) {
input.parse().map(PyFunctionOption::Signature)
} else {
Err(lookahead.error())
}
}
}
impl PyFunctionOptions {
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> { pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
let mut from_py_with = None; let mut options = PyFunctionOptions::default();
options.take_pyo3_attributes(attrs)?;
Ok(options)
}
for meta in extract_pyo3_metas(attrs)? { pub fn take_pyo3_attributes(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
let meta = match meta { take_attributes(attrs, |attr| {
NestedMeta::Meta(meta) => meta, if let Some(pyo3_attributes) = get_pyo3_attribute(attr)? {
NestedMeta::Lit(lit) => { self.add_attributes(pyo3_attributes)?;
bail_spanned!(lit.span() => "expected `from_py_with`, got a literal") Ok(true)
} } else if let Some(name) = get_deprecated_name_attribute(attr)? {
}; self.set_name(name)?;
self.name_is_deprecated = true;
if meta.path().is_ident("from_py_with") { Ok(true)
from_py_with = Some(FromPyWithAttribute::from_meta(meta)?);
} else { } else {
bail_spanned!(meta.span() => "only `from_py_with` is supported") Ok(false)
}
})?;
Ok(())
}
pub fn add_attributes(
&mut self,
attrs: impl IntoIterator<Item = PyFunctionOption>,
) -> Result<()> {
for attr in attrs {
match attr {
PyFunctionOption::Name(name) => self.set_name(name)?,
PyFunctionOption::PassModule(kw) => {
ensure_spanned!(
!self.pass_module,
kw.span() => "`pass_module` may only be specified once"
);
self.pass_module = true;
}
PyFunctionOption::Signature(signature) => {
ensure_spanned!(
self.signature.is_none(),
// FIXME: improve the span of this error message
Span::call_site() => "`signature` may only be specified once"
);
self.signature = Some(signature);
}
} }
} }
Ok(())
Ok(PyFunctionArgAttrs { from_py_with })
} }
pub fn set_name(&mut self, name: NameAttribute) -> Result<()> {
ensure_spanned!(
self.name.is_none(),
name.0.span() => "`name` may only be specified once"
);
self.name = Some(name);
Ok(())
}
}
pub fn build_py_function(
ast: &mut syn::ItemFn,
mut options: PyFunctionOptions,
) -> syn::Result<TokenStream> {
options.take_pyo3_attributes(&mut ast.attrs)?;
Ok(impl_wrap_pyfunction(ast, options)?.1)
}
/// Coordinates the naming of a the add-function-to-python-module function
fn function_wrapper_ident(name: &Ident) -> Ident {
// Make sure this ident matches the one of wrap_pyfunction
format_ident!("__pyo3_get_function_{}", name)
}
/// Generates python wrapper over a function that allows adding it to a python module as a python
/// function
pub fn impl_wrap_pyfunction(
func: &mut syn::ItemFn,
options: PyFunctionOptions,
) -> syn::Result<(Ident, TokenStream)> {
check_generic(&func.sig)?;
let python_name = options
.name
.map_or_else(|| func.sig.ident.unraw(), |name| name.0);
let signature = options.signature.unwrap_or_default();
let mut arguments = func
.sig
.inputs
.iter_mut()
.map(FnArg::parse)
.collect::<syn::Result<Vec<_>>>()?;
if options.pass_module {
const PASS_MODULE_ERR: &str = "expected &PyModule as first argument with `pass_module`";
ensure_spanned!(
!arguments.is_empty(),
func.span() => PASS_MODULE_ERR
);
let arg = arguments.remove(0);
ensure_spanned!(
type_is_pymodule(arg.ty),
arg.ty.span() => PASS_MODULE_ERR
);
}
let ty = method::get_return_info(&func.sig.output);
let text_signature = utils::parse_text_signature_attrs(&mut func.attrs, &python_name)?;
let doc = utils::get_doc(&func.attrs, text_signature, true)?;
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
let spec = method::FnSpec {
tp: method::FnType::FnStatic,
name: &function_wrapper_ident,
python_name,
attrs: signature.arguments,
args: arguments,
output: ty,
doc,
name_is_deprecated: options.name_is_deprecated,
};
let doc = &spec.doc;
let python_name = spec.python_name_with_deprecation();
let name = &func.sig.ident;
let wrapper_ident = format_ident!("__pyo3_raw_{}", name);
let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, options.pass_module)?;
let wrapped_pyfunction = quote! {
#wrapper
pub(crate) fn #function_wrapper_ident<'a>(
args: impl Into<pyo3::derive_utils::PyFunctionArguments<'a>>
) -> pyo3::PyResult<&'a pyo3::types::PyCFunction> {
pyo3::types::PyCFunction::internal_new(
pyo3::class::methods::PyMethodDef::cfunction_with_keywords(
#python_name,
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper_ident),
#doc,
),
args.into(),
)
}
};
Ok((function_wrapper_ident, wrapped_pyfunction))
}
/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
fn function_c_wrapper(
name: &Ident,
wrapper_ident: &Ident,
spec: &FnSpec<'_>,
pass_module: bool,
) -> Result<TokenStream> {
let names: Vec<Ident> = get_arg_names(&spec);
let cb;
let slf_module;
if pass_module {
cb = quote! {
pyo3::callback::convert(_py, #name(_slf, #(#names),*))
};
slf_module = Some(quote! {
let _slf = _py.from_borrowed_ptr::<pyo3::types::PyModule>(_slf);
});
} else {
cb = quote! {
pyo3::callback::convert(_py, #name(#(#names),*))
};
slf_module = None;
};
let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, None, cb, &py)?;
Ok(quote! {
unsafe extern "C" fn #wrapper_ident(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
pyo3::callback::handle_panic(|#py| {
#slf_module
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body
})
}
})
}
fn type_is_pymodule(ty: &syn::Type) -> bool {
if let syn::Type::Reference(tyref) = ty {
if let syn::Type::Path(typath) = tyref.elem.as_ref() {
if typath
.path
.segments
.last()
.map(|seg| seg.ident == "PyModule")
.unwrap_or(false)
{
return true;
}
}
}
false
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::{Argument, PyFunctionAttr}; use super::{Argument, PyFunctionSignature};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::parse_quote; use syn::parse_quote;
fn items(input: TokenStream) -> syn::Result<Vec<Argument>> { fn items(input: TokenStream) -> syn::Result<Vec<Argument>> {
let py_fn_attr: PyFunctionAttr = syn::parse2(input)?; let py_fn_attr: PyFunctionSignature = syn::parse2(input)?;
Ok(py_fn_attr.arguments) Ok(py_fn_attr.arguments)
} }

View File

@ -1,6 +1,10 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::pymethod; use crate::{
konst::{ConstAttributes, ConstSpec},
pyfunction::PyFunctionOptions,
pymethod,
};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use pymethod::GeneratedPyMethod; use pymethod::GeneratedPyMethod;
use quote::quote; use quote::quote;
@ -39,7 +43,8 @@ pub fn impl_methods(
for iimpl in impls.iter_mut() { for iimpl in impls.iter_mut() {
match iimpl { match iimpl {
syn::ImplItem::Method(meth) => { syn::ImplItem::Method(meth) => {
match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs)? { let options = PyFunctionOptions::from_attrs(&mut meth.attrs)?;
match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs, options)? {
GeneratedPyMethod::Method(token_stream) => { GeneratedPyMethod::Method(token_stream) => {
let attrs = get_cfg_attributes(&meth.attrs); let attrs = get_cfg_attributes(&meth.attrs);
methods.push(quote!(#(#attrs)* #token_stream)); methods.push(quote!(#(#attrs)* #token_stream));
@ -55,8 +60,14 @@ pub fn impl_methods(
} }
} }
syn::ImplItem::Const(konst) => { syn::ImplItem::Const(konst) => {
if let Some(meth) = pymethod::gen_py_const(ty, &konst.ident, &mut konst.attrs)? { let attributes = ConstAttributes::from_attrs(&mut konst.attrs)?;
if attributes.is_class_attr {
let spec = ConstSpec {
rust_ident: konst.ident.clone(),
attributes,
};
let attrs = get_cfg_attributes(&konst.attrs); let attrs = get_cfg_attributes(&konst.attrs);
let meth = pymethod::gen_py_const(ty, &spec);
methods.push(quote!(#(#attrs)* #meth)); methods.push(quote!(#(#attrs)* #meth));
} }
} }

View File

@ -1,14 +1,17 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::attrs::FromPyWithAttribute;
use crate::konst::ConstSpec;
use crate::method::{FnArg, FnSpec, FnType, SelfType};
use crate::utils; use crate::utils;
use crate::{attributes::FromPyWithAttribute, konst::ConstSpec};
use crate::{
method::{FnArg, FnSpec, FnType, SelfType},
pyfunction::PyFunctionOptions,
};
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned}; use quote::{quote, quote_spanned};
use syn::{ext::IdentExt, spanned::Spanned, Result}; use syn::{ext::IdentExt, spanned::Spanned, Result};
#[derive(Clone, Copy)]
pub enum PropertyType<'a> { pub enum PropertyType<'a> {
Descriptor(&'a syn::Field), Descriptor(&'a syn::Ident),
Function(&'a FnSpec<'a>), Function(&'a FnSpec<'a>),
} }
@ -22,9 +25,10 @@ pub fn gen_py_method(
cls: &syn::Type, cls: &syn::Type,
sig: &mut syn::Signature, sig: &mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>, meth_attrs: &mut Vec<syn::Attribute>,
options: PyFunctionOptions,
) -> Result<GeneratedPyMethod> { ) -> Result<GeneratedPyMethod> {
check_generic(sig)?; check_generic(sig)?;
let spec = FnSpec::parse(sig, &mut *meth_attrs, true)?; let spec = FnSpec::parse(sig, &mut *meth_attrs, options)?;
Ok(match &spec.tp { Ok(match &spec.tp {
FnType::Fn(self_ty) => { FnType::Fn(self_ty) => {
@ -43,14 +47,12 @@ pub fn gen_py_method(
cls, cls,
PropertyType::Function(&spec), PropertyType::Function(&spec),
self_ty, self_ty,
&spec.python_name,
&spec.doc, &spec.doc,
)?), )?),
FnType::Setter(self_ty) => GeneratedPyMethod::Method(impl_py_setter_def( FnType::Setter(self_ty) => GeneratedPyMethod::Method(impl_py_setter_def(
cls, cls,
PropertyType::Function(&spec), PropertyType::Function(&spec),
self_ty, self_ty,
&spec.python_name,
&spec.doc, &spec.doc,
)?), )?),
}) })
@ -68,22 +70,15 @@ pub(crate) fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
Ok(()) Ok(())
} }
pub fn gen_py_const( pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
cls: &syn::Type, let member = &spec.rust_ident;
name: &syn::Ident, let wrapper = quote! {{
attrs: &mut Vec<syn::Attribute>, fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
) -> syn::Result<Option<TokenStream>> { pyo3::IntoPy::into_py(#cls::#member, py)
let spec = ConstSpec::parse(name, attrs)?; }
if spec.is_class_attr { __wrap
let wrapper = quote! {{ }};
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject { impl_py_const_class_attribute(&spec, &wrapper)
pyo3::IntoPy::into_py(#cls::#name, py)
}
__wrap
}};
return Ok(Some(impl_py_const_class_attribute(&spec, &wrapper)));
}
Ok(None)
} }
/// Generate function wrapper for PyCFunctionWithKeywords /// Generate function wrapper for PyCFunctionWithKeywords
@ -255,12 +250,9 @@ pub(crate) fn impl_wrap_getter(
property_type: PropertyType, property_type: PropertyType,
self_ty: &SelfType, self_ty: &SelfType,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let getter_impl = match property_type { let getter_impl = match &property_type {
PropertyType::Descriptor(field) => { PropertyType::Descriptor(ident) => {
let name = field.ident.as_ref().unwrap(); quote!(_slf.#ident.clone())
quote!({
_slf.#name.clone()
})
} }
PropertyType::Function(spec) => impl_call_getter(cls, spec)?, PropertyType::Function(spec) => impl_call_getter(cls, spec)?,
}; };
@ -307,10 +299,9 @@ pub(crate) fn impl_wrap_setter(
property_type: PropertyType, property_type: PropertyType,
self_ty: &SelfType, self_ty: &SelfType,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let setter_impl = match property_type { let setter_impl = match &property_type {
PropertyType::Descriptor(field) => { PropertyType::Descriptor(ident) => {
let name = field.ident.as_ref().unwrap(); quote!({ _slf.#ident = _val; })
quote!({ _slf.#name = _val; })
} }
PropertyType::Function(spec) => impl_call_setter(cls, spec)?, PropertyType::Function(spec) => impl_call_setter(cls, spec)?,
}; };
@ -584,14 +575,14 @@ pub fn impl_py_method_def(
flags: Option<TokenStream>, flags: Option<TokenStream>,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
let add_flags = flags.map(|flags| quote!(.flags(#flags))); let add_flags = flags.map(|flags| quote!(.flags(#flags)));
let python_name = &spec.python_name; let python_name = spec.python_name_with_deprecation();
let doc = &spec.doc; let doc = &spec.doc;
if spec.args.is_empty() { if spec.args.is_empty() {
let wrapper = impl_wrap_noargs(cls, spec, self_ty); let wrapper = impl_wrap_noargs(cls, spec, self_ty);
Ok(quote! { Ok(quote! {
pyo3::class::PyMethodDefType::Method({ pyo3::class::PyMethodDefType::Method({
pyo3::class::PyMethodDef::noargs( pyo3::class::PyMethodDef::noargs(
concat!(stringify!(#python_name), "\0"), #python_name,
pyo3::class::methods::PyCFunction(#wrapper), pyo3::class::methods::PyCFunction(#wrapper),
#doc #doc
) )
@ -604,7 +595,7 @@ pub fn impl_py_method_def(
Ok(quote! { Ok(quote! {
pyo3::class::PyMethodDefType::Method({ pyo3::class::PyMethodDefType::Method({
pyo3::class::PyMethodDef::cfunction_with_keywords( pyo3::class::PyMethodDef::cfunction_with_keywords(
concat!(stringify!(#python_name), "\0"), #python_name,
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper), pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
#doc #doc
) )
@ -627,12 +618,12 @@ pub fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStr
pub fn impl_py_method_def_class(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> { pub fn impl_py_method_def_class(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
let wrapper = impl_wrap_class(cls, &spec)?; let wrapper = impl_wrap_class(cls, &spec)?;
let python_name = &spec.python_name; let python_name = spec.python_name_with_deprecation();
let doc = &spec.doc; let doc = &spec.doc;
Ok(quote! { Ok(quote! {
pyo3::class::PyMethodDefType::Class({ pyo3::class::PyMethodDefType::Class({
pyo3::class::PyMethodDef::cfunction_with_keywords( pyo3::class::PyMethodDef::cfunction_with_keywords(
concat!(stringify!(#python_name), "\0"), #python_name,
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper), pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
#doc #doc
).flags(pyo3::ffi::METH_CLASS) ).flags(pyo3::ffi::METH_CLASS)
@ -642,12 +633,12 @@ pub fn impl_py_method_def_class(cls: &syn::Type, spec: &FnSpec) -> Result<TokenS
pub fn impl_py_method_def_static(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> { pub fn impl_py_method_def_static(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
let wrapper = impl_wrap_static(cls, &spec)?; let wrapper = impl_wrap_static(cls, &spec)?;
let python_name = &spec.python_name; let python_name = spec.python_name_with_deprecation();
let doc = &spec.doc; let doc = &spec.doc;
Ok(quote! { Ok(quote! {
pyo3::class::PyMethodDefType::Static({ pyo3::class::PyMethodDefType::Static({
pyo3::class::PyMethodDef::cfunction_with_keywords( pyo3::class::PyMethodDef::cfunction_with_keywords(
concat!(stringify!(#python_name), "\0"), #python_name,
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper), pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
#doc #doc
).flags(pyo3::ffi::METH_STATIC) ).flags(pyo3::ffi::METH_STATIC)
@ -657,11 +648,11 @@ pub fn impl_py_method_def_static(cls: &syn::Type, spec: &FnSpec) -> Result<Token
pub fn impl_py_method_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream { pub fn impl_py_method_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
let wrapper = impl_wrap_class_attribute(cls, &spec); let wrapper = impl_wrap_class_attribute(cls, &spec);
let python_name = &spec.python_name; let python_name = spec.python_name_with_deprecation();
quote! { quote! {
pyo3::class::PyMethodDefType::ClassAttribute({ pyo3::class::PyMethodDefType::ClassAttribute({
pyo3::class::PyClassAttributeDef::new( pyo3::class::PyClassAttributeDef::new(
concat!(stringify!(#python_name), "\0"), #python_name,
pyo3::class::methods::PyClassAttributeFactory(#wrapper) pyo3::class::methods::PyClassAttributeFactory(#wrapper)
) )
}) })
@ -669,14 +660,16 @@ pub fn impl_py_method_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenSt
} }
pub fn impl_py_const_class_attribute(spec: &ConstSpec, wrapper: &TokenStream) -> TokenStream { pub fn impl_py_const_class_attribute(spec: &ConstSpec, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.python_name; let python_name = &spec.python_name_with_deprecation();
quote! { quote! {
pyo3::class::PyMethodDefType::ClassAttribute({ {
pyo3::class::PyClassAttributeDef::new( pyo3::class::PyMethodDefType::ClassAttribute({
concat!(stringify!(#python_name), "\0"), pyo3::class::PyClassAttributeDef::new(
pyo3::class::methods::PyClassAttributeFactory(#wrapper) #python_name,
) pyo3::class::methods::PyClassAttributeFactory(#wrapper)
}) )
})
}
} }
} }
@ -699,14 +692,20 @@ pub(crate) fn impl_py_setter_def(
cls: &syn::Type, cls: &syn::Type,
property_type: PropertyType, property_type: PropertyType,
self_ty: &SelfType, self_ty: &SelfType,
python_name: &syn::Ident,
doc: &syn::LitStr, doc: &syn::LitStr,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
let python_name = match property_type {
PropertyType::Descriptor(ident) => {
let formatted_name = format!("{}\0", ident.unraw());
quote!(#formatted_name)
}
PropertyType::Function(spec) => spec.python_name_with_deprecation(),
};
let wrapper = impl_wrap_setter(cls, property_type, self_ty)?; let wrapper = impl_wrap_setter(cls, property_type, self_ty)?;
Ok(quote! { Ok(quote! {
pyo3::class::PyMethodDefType::Setter({ pyo3::class::PyMethodDefType::Setter({
pyo3::class::PySetterDef::new( pyo3::class::PySetterDef::new(
concat!(stringify!(#python_name), "\0"), #python_name,
pyo3::class::methods::PySetter(#wrapper), pyo3::class::methods::PySetter(#wrapper),
#doc #doc
) )
@ -718,14 +717,20 @@ pub(crate) fn impl_py_getter_def(
cls: &syn::Type, cls: &syn::Type,
property_type: PropertyType, property_type: PropertyType,
self_ty: &SelfType, self_ty: &SelfType,
python_name: &syn::Ident,
doc: &syn::LitStr, doc: &syn::LitStr,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
let python_name = match property_type {
PropertyType::Descriptor(ident) => {
let formatted_name = format!("{}\0", ident.unraw());
quote!(#formatted_name)
}
PropertyType::Function(spec) => spec.python_name_with_deprecation(),
};
let wrapper = impl_wrap_getter(cls, property_type, self_ty)?; let wrapper = impl_wrap_getter(cls, property_type, self_ty)?;
Ok(quote! { Ok(quote! {
pyo3::class::PyMethodDefType::Getter({ pyo3::class::PyMethodDefType::Getter({
pyo3::class::PyGetterDef::new( pyo3::class::PyGetterDef::new(
concat!(stringify!(#python_name), "\0"), #python_name,
pyo3::class::methods::PyGetter(#wrapper), pyo3::class::methods::PyGetter(#wrapper),
#doc #doc
) )

View File

@ -3,6 +3,7 @@
use crate::defs; use crate::defs;
use crate::method::{FnSpec, FnType}; use crate::method::{FnSpec, FnType};
use crate::proto_method::impl_method_proto; use crate::proto_method::impl_method_proto;
use crate::pyfunction::PyFunctionOptions;
use crate::pymethod; use crate::pymethod;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
@ -62,7 +63,8 @@ fn impl_proto_impl(
} }
// Add non-slot methods to inventory like `#[pymethods]` // Add non-slot methods to inventory like `#[pymethods]`
if let Some(m) = proto.get_method(&met.sig.ident) { if let Some(m) = proto.get_method(&met.sig.ident) {
let fn_spec = FnSpec::parse(&mut met.sig, &mut met.attrs, false)?; let fn_spec =
FnSpec::parse(&mut met.sig, &mut met.attrs, PyFunctionOptions::default())?;
let flags = if m.can_coexist { let flags = if m.can_coexist {
// We need METH_COEXIST here to prevent __add__ from overriding __radd__ // We need METH_COEXIST here to prevent __add__ from overriding __radd__

View File

@ -1,5 +1,6 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use proc_macro2::Span; use proc_macro2::{Span, TokenStream};
use quote::quote_spanned;
use syn::spanned::Spanned; use syn::spanned::Spanned;
/// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span. /// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span.
@ -164,3 +165,14 @@ pub fn get_doc(
Ok(syn::LitStr::new(&doc, span)) Ok(syn::LitStr::new(&doc, span))
} }
pub fn name_deprecation_token(span: Span, name_is_deprecated: bool) -> Option<TokenStream> {
if name_is_deprecated {
Some(quote_spanned!(
span =>
let _ = pyo3::impl_::deprecations::NAME_ATTRIBUTE;
))
} else {
None
}
}

View File

@ -9,7 +9,7 @@ use proc_macro::TokenStream;
use pyo3_macros_backend::{ use pyo3_macros_backend::{
build_derive_from_pyobject, build_py_class, build_py_function, build_py_methods, build_derive_from_pyobject, build_py_class, build_py_function, build_py_methods,
build_py_proto, get_doc, process_functions_in_module, py_init, PyClassArgs, PyClassMethodsType, build_py_proto, get_doc, process_functions_in_module, py_init, PyClassArgs, PyClassMethodsType,
PyFunctionAttr, PyFunctionOptions,
}; };
use quote::quote; use quote::quote;
use syn::parse_macro_input; use syn::parse_macro_input;
@ -217,9 +217,9 @@ pub fn pymethods_with_inventory(_: TokenStream, input: TokenStream) -> TokenStre
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream { pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemFn); let mut ast = parse_macro_input!(input as syn::ItemFn);
let args = parse_macro_input!(attr as PyFunctionAttr); let options = parse_macro_input!(attr as PyFunctionOptions);
let expanded = build_py_function(&mut ast, args).unwrap_or_else(|e| e.to_compile_error()); let expanded = build_py_function(&mut ast, options).unwrap_or_else(|e| e.to_compile_error());
quote!( quote!(
#ast #ast

13
src/impl_/mod.rs Normal file
View File

@ -0,0 +1,13 @@
//! Internals of PyO3 which are accessed by code expanded from PyO3's procedural macros. Usage of
//! any of these APIs in downstream code is implicitly acknowledging that these APIs may change at
//! any time without documentation in the CHANGELOG and without breaking semver guarantees.
/// Symbols to represent deprecated uses of PyO3's macros.
pub mod deprecations {
#[doc(hidden)]
#[deprecated(
since = "0.14.0",
note = "use `#[pyo3(name = \"...\")]` instead of `#[name = \"...\"]`"
)]
pub const NAME_ATTRIBUTE: () = ();
}

View File

@ -177,6 +177,7 @@ pub mod exceptions;
pub mod ffi; pub mod ffi;
pub mod freelist; pub mod freelist;
mod gil; mod gil;
pub mod impl_;
mod instance; mod instance;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]

View File

@ -19,13 +19,17 @@ impl Foo {
#[classattr] #[classattr]
const MY_CONST: &'static str = "foobar"; const MY_CONST: &'static str = "foobar";
#[classattr]
#[pyo3(name = "RENAMED_CONST")]
const MY_CONST_2: &'static str = "foobar_2";
#[classattr] #[classattr]
fn a() -> i32 { fn a() -> i32 {
5 5
} }
#[classattr] #[classattr]
#[name = "B"] #[pyo3(name = "B")]
fn b() -> String { fn b() -> String {
"bar".to_string() "bar".to_string()
} }
@ -46,9 +50,11 @@ fn class_attributes() {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let foo_obj = py.get_type::<Foo>(); let foo_obj = py.get_type::<Foo>();
py_assert!(py, foo_obj, "foo_obj.MY_CONST == 'foobar'");
py_assert!(py, foo_obj, "foo_obj.RENAMED_CONST == 'foobar_2'");
py_assert!(py, foo_obj, "foo_obj.a == 5"); py_assert!(py, foo_obj, "foo_obj.a == 5");
py_assert!(py, foo_obj, "foo_obj.B == 'bar'"); py_assert!(py, foo_obj, "foo_obj.B == 'bar'");
py_assert!(py, foo_obj, "foo_obj.MY_CONST == 'foobar'"); py_assert!(py, foo_obj, "foo_obj.foo.x == 1");
} }
// Ignored because heap types are not immutable: // Ignored because heap types are not immutable:

View File

@ -86,15 +86,15 @@ struct EmptyClass2 {}
#[pymethods] #[pymethods]
impl EmptyClass2 { impl EmptyClass2 {
#[name = "custom_fn"] #[pyo3(name = "custom_fn")]
fn bar(&self) {} fn bar(&self) {}
#[staticmethod] #[staticmethod]
#[name = "custom_static"] #[pyo3(name = "custom_static")]
fn bar_static() {} fn bar_static() {}
#[getter] #[getter]
#[name = "custom_getter"] #[pyo3(name = "custom_getter")]
fn foo(&self) -> i32 { fn foo(&self) -> i32 {
5 5
} }

View File

@ -34,6 +34,7 @@ fn test_compile_errors() {
#[rustversion::since(1.49)] #[rustversion::since(1.49)]
fn tests_rust_1_49(t: &trybuild::TestCases) { fn tests_rust_1_49(t: &trybuild::TestCases) {
t.compile_fail("tests/ui/deprecations.rs");
t.compile_fail("tests/ui/invalid_frompy_derive.rs"); t.compile_fail("tests/ui/invalid_frompy_derive.rs");
t.compile_fail("tests/ui/invalid_pymethod_receiver.rs"); t.compile_fail("tests/ui/invalid_pymethod_receiver.rs");
t.compile_fail("tests/ui/pyclass_send.rs"); t.compile_fail("tests/ui/pyclass_send.rs");

View File

@ -192,7 +192,7 @@ fn test_raw_idents() {
} }
#[pyfunction] #[pyfunction]
#[name = "foobar"] #[pyo3(name = "foobar")]
fn custom_named_fn() -> usize { fn custom_named_fn() -> usize {
42 42
} }

33
tests/ui/deprecations.rs Normal file
View File

@ -0,0 +1,33 @@
#![deny(deprecated)]
use pyo3::prelude::*;
#[pyclass]
struct TestClass {
num: u32,
}
#[pymethods]
impl TestClass {
#[classattr]
#[name = "num"]
const DEPRECATED_NAME_CONSTANT: i32 = 0;
#[name = "num"]
fn deprecated_name_pymethod(&self) { }
#[staticmethod]
#[name = "custom_static"]
fn deprecated_name_staticmethod() {}
}
#[pyfunction]
#[name = "foo"]
fn deprecated_name_pyfunction() { }
fn main() {
}
// TODO: ensure name deprecated on #[pyfunction] and #[pymodule]

View File

@ -0,0 +1,29 @@
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> $DIR/deprecations.rs:13:5
|
13 | #[name = "num"]
| ^
|
note: the lint level is defined here
--> $DIR/deprecations.rs:1:9
|
1 | #![deny(deprecated)]
| ^^^^^^^^^^
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> $DIR/deprecations.rs:16:5
|
16 | #[name = "num"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> $DIR/deprecations.rs:20:5
|
20 | #[name = "custom_static"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> $DIR/deprecations.rs:25:1
|
25 | #[name = "foo"]
| ^

View File

@ -1,22 +1,22 @@
error: only `from_py_with` is supported error: expected `from_py_with`
--> $DIR/invalid_argument_attributes.rs:4:29 --> $DIR/invalid_argument_attributes.rs:4:29
| |
4 | fn invalid_attribute(#[pyo3(get)] param: String) {} 4 | fn invalid_attribute(#[pyo3(get)] param: String) {}
| ^^^ | ^^^
error: expected a name-value: `pyo3(from_py_with = "func")` error: expected `=`
--> $DIR/invalid_argument_attributes.rs:7:33 --> $DIR/invalid_argument_attributes.rs:7:32
| |
7 | fn from_py_with_no_value(#[pyo3(from_py_with)] param: String) {} 7 | fn from_py_with_no_value(#[pyo3(from_py_with)] param: String) {}
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
error: expected `from_py_with`, got a literal error: expected `from_py_with`
--> $DIR/invalid_argument_attributes.rs:10:31 --> $DIR/invalid_argument_attributes.rs:10:31
| |
10 | fn from_py_with_string(#[pyo3("from_py_with")] param: String) {} 10 | fn from_py_with_string(#[pyo3("from_py_with")] param: String) {}
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
error: expected literal error: expected string literal
--> $DIR/invalid_argument_attributes.rs:13:58 --> $DIR/invalid_argument_attributes.rs:13:58
| |
13 | fn from_py_with_value_not_a_string(#[pyo3(from_py_with = func)] param: String) {} 13 | fn from_py_with_value_not_a_string(#[pyo3(from_py_with = func)] param: String) {}

View File

@ -84,23 +84,23 @@ error: transparent structs and variants can only have 1 field
70 | | }, 70 | | },
| |_____^ | |_____^
error: expected `attribute`, `item` or `from_py_with` error: expected one of: `attribute`, `item`, `from_py_with`
--> $DIR/invalid_frompy_derive.rs:76:12 --> $DIR/invalid_frompy_derive.rs:76:12
| |
76 | #[pyo3(attr)] 76 | #[pyo3(attr)]
| ^^^^ | ^^^^
error: expected a single string literal argument error: expected string literal
--> $DIR/invalid_frompy_derive.rs:82:12 --> $DIR/invalid_frompy_derive.rs:82:22
| |
82 | #[pyo3(attribute(1))] 82 | #[pyo3(attribute(1))]
| ^^^^^^^^^ | ^
error: expected a single string literal argument error: expected at most one argument: `attribute` or `attribute("name")`
--> $DIR/invalid_frompy_derive.rs:88:12 --> $DIR/invalid_frompy_derive.rs:88:25
| |
88 | #[pyo3(attribute("a", "b"))] 88 | #[pyo3(attribute("a", "b"))]
| ^^^^^^^^^ | ^
error: attribute name cannot be empty error: attribute name cannot be empty
--> $DIR/invalid_frompy_derive.rs:94:22 --> $DIR/invalid_frompy_derive.rs:94:22
@ -108,43 +108,43 @@ error: attribute name cannot be empty
94 | #[pyo3(attribute(""))] 94 | #[pyo3(attribute(""))]
| ^^ | ^^
error: expected a single string literal argument error: unexpected end of input, expected string literal
--> $DIR/invalid_frompy_derive.rs:100:12 --> $DIR/invalid_frompy_derive.rs:100:21
| |
100 | #[pyo3(attribute())] 100 | #[pyo3(attribute())]
| ^^^^^^^^^ | ^^
error: expected a single literal argument error: expected at most one argument: `item` or `item(key)`
--> $DIR/invalid_frompy_derive.rs:106:12 --> $DIR/invalid_frompy_derive.rs:106:20
| |
106 | #[pyo3(item("a", "b"))] 106 | #[pyo3(item("a", "b"))]
| ^^^^ | ^
error: expected a single literal argument error: unexpected end of input, expected literal
--> $DIR/invalid_frompy_derive.rs:112:12 --> $DIR/invalid_frompy_derive.rs:112:16
| |
112 | #[pyo3(item())] 112 | #[pyo3(item())]
| ^^^^ | ^^
error: only one of `attribute` or `item` can be provided error: only one of `attribute` or `item` can be provided
--> $DIR/invalid_frompy_derive.rs:118:18 --> $DIR/invalid_frompy_derive.rs:118:5
| |
118 | #[pyo3(item, attribute)] 118 | #[pyo3(item, attribute)]
| ^^^^^^^^^ | ^
error: unknown `pyo3` container attribute error: expected `transparent` or `annotation`
--> $DIR/invalid_frompy_derive.rs:123:8 --> $DIR/invalid_frompy_derive.rs:123:8
| |
123 | #[pyo3(unknown = "should not work")] 123 | #[pyo3(unknown = "should not work")]
| ^^^^^^^ | ^^^^^^^
error: annotation is not supported for structs error: `annotation` is unsupported for structs
--> $DIR/invalid_frompy_derive.rs:129:21 --> $DIR/invalid_frompy_derive.rs:129:21
| |
129 | #[pyo3(annotation = "should not work")] 129 | #[pyo3(annotation = "should not work")]
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
error: expected string literal for annotation error: expected string literal
--> $DIR/invalid_frompy_derive.rs:136:25 --> $DIR/invalid_frompy_derive.rs:136:25
| |
136 | #[pyo3(annotation = 1)] 136 | #[pyo3(annotation = 1)]
@ -170,13 +170,13 @@ error: cannot derive FromPyObject for empty structs and variants
| |
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected a name-value: `pyo3(from_py_with = "func")` error: expected `=`
--> $DIR/invalid_frompy_derive.rs:158:12 --> $DIR/invalid_frompy_derive.rs:158:11
| |
158 | #[pyo3(from_py_with)] 158 | #[pyo3(from_py_with)]
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
error: expected literal error: expected string literal
--> $DIR/invalid_frompy_derive.rs:164:27 --> $DIR/invalid_frompy_derive.rs:164:27
| |
164 | #[pyo3(from_py_with = func)] 164 | #[pyo3(from_py_with = func)]

View File

@ -16,7 +16,7 @@ error: setter function can have at most two arguments ([pyo3::Python,] and value
24 | fn setter_with_too_many_args(&mut self, py: Python, foo: u32, bar: u32) {} 24 | fn setter_with_too_many_args(&mut self, py: Python, foo: u32, bar: u32) {}
| ^^^ | ^^^
error: get/set are not supported on tuple struct field error: `#[pyo3(get, set)]` is not supported on tuple struct fields
--> $DIR/invalid_property_args.rs:28:44 --> $DIR/invalid_property_args.rs:28:44
| |
28 | struct TupleGetterSetter(#[pyo3(get, set)] i32); 28 | struct TupleGetterSetter(#[pyo3(get, set)] i32);

View File

@ -7,21 +7,21 @@ struct TestClass {
#[pymethods] #[pymethods]
impl TestClass { impl TestClass {
#[name = "num"] #[pyo3(name = "num")]
#[getter(number)] #[getter(number)]
fn get_num(&self) -> u32 { self.num } fn get_num(&self) -> u32 { self.num }
} }
#[pymethods] #[pymethods]
impl TestClass { impl TestClass {
#[name = "foo"] #[pyo3(name = "foo")]
#[name = "bar"] #[pyo3(name = "bar")]
fn qux(&self) -> u32 { self.num } fn qux(&self) -> u32 { self.num }
} }
#[pymethods] #[pymethods]
impl TestClass { impl TestClass {
#[name = "makenew"] #[pyo3(name = "makenew")]
#[new] #[new]
fn new(&self) -> Self { Self { num: 0 } } fn new(&self) -> Self { Self { num: 0 } }
} }

View File

@ -1,17 +1,17 @@
error: name cannot be specified twice error: `name` may only be specified once
--> $DIR/invalid_pymethod_names.rs:10:5 --> $DIR/invalid_pymethod_names.rs:10:19
| |
10 | #[name = "num"] 10 | #[pyo3(name = "num")]
| ^ | ^^^^^
error: #[name] can not be specified multiple times error: `name` may only be specified once
--> $DIR/invalid_pymethod_names.rs:18:5 --> $DIR/invalid_pymethod_names.rs:18:19
| |
18 | #[name = "bar"] 18 | #[pyo3(name = "bar")]
| ^ | ^^^^^
error: name not allowed with this method type error: `name` not allowed with `#[new]`
--> $DIR/invalid_pymethod_names.rs:24:5 --> $DIR/invalid_pymethod_names.rs:24:19
| |
24 | #[name = "makenew"] 24 | #[pyo3(name = "makenew")]
| ^ | ^^^^^^^^^

View File

@ -9,6 +9,12 @@ impl MyClass {
fn class_attr_with_args(foo: i32) {} fn class_attr_with_args(foo: i32) {}
} }
#[pymethods]
impl MyClass {
#[classattr(foobar)]
const CLASS_ATTR_WITH_ATTRIBUTE_ARG: i32 = 3;
}
#[pymethods] #[pymethods]
impl MyClass { impl MyClass {
fn staticmethod_without_attribute() {} fn staticmethod_without_attribute() {}

View File

@ -4,80 +4,86 @@ error: class attribute methods cannot take arguments
9 | fn class_attr_with_args(foo: i32) {} 9 | fn class_attr_with_args(foo: i32) {}
| ^^^ | ^^^
error: static method needs #[staticmethod] attribute error: `#[classattr]` does not take any arguments
--> $DIR/invalid_pymethods.rs:14:5 --> $DIR/invalid_pymethods.rs:14:5
| |
14 | fn staticmethod_without_attribute() {} 14 | #[classattr(foobar)]
| ^
error: static method needs #[staticmethod] attribute
--> $DIR/invalid_pymethods.rs:20:5
|
20 | fn staticmethod_without_attribute() {}
| ^^ | ^^
error: unexpected receiver error: unexpected receiver
--> $DIR/invalid_pymethods.rs:20:35 --> $DIR/invalid_pymethods.rs:26:35
| |
20 | fn staticmethod_with_receiver(&self) {} 26 | fn staticmethod_with_receiver(&self) {}
| ^ | ^
error: expected receiver for #[getter] error: expected receiver for #[getter]
--> $DIR/invalid_pymethods.rs:33:5 --> $DIR/invalid_pymethods.rs:39:5
| |
33 | fn getter_without_receiver() {} 39 | fn getter_without_receiver() {}
| ^^ | ^^
error: expected receiver for #[setter] error: expected receiver for #[setter]
--> $DIR/invalid_pymethods.rs:39:5 --> $DIR/invalid_pymethods.rs:45:5
| |
39 | fn setter_without_receiver() {} 45 | fn setter_without_receiver() {}
| ^^ | ^^
error: text_signature not allowed on __new__; if you want to add a signature on __new__, put it on the struct definition instead error: text_signature not allowed on __new__; if you want to add a signature on __new__, put it on the struct definition instead
--> $DIR/invalid_pymethods.rs:45:24 --> $DIR/invalid_pymethods.rs:51:24
| |
45 | #[text_signature = "()"] 51 | #[text_signature = "()"]
| ^^^^ | ^^^^
error: text_signature not allowed with this method type error: text_signature not allowed with this method type
--> $DIR/invalid_pymethods.rs:52:24 --> $DIR/invalid_pymethods.rs:58:24
| |
52 | #[text_signature = "()"] 58 | #[text_signature = "()"]
| ^^^^ | ^^^^
error: text_signature not allowed with this method type error: text_signature not allowed with this method type
--> $DIR/invalid_pymethods.rs:59:24 --> $DIR/invalid_pymethods.rs:65:24
| |
59 | #[text_signature = "()"] 65 | #[text_signature = "()"]
| ^^^^ | ^^^^
error: text_signature not allowed with this method type error: text_signature not allowed with this method type
--> $DIR/invalid_pymethods.rs:66:24 --> $DIR/invalid_pymethods.rs:72:24
| |
66 | #[text_signature = "()"] 72 | #[text_signature = "()"]
| ^^^^ | ^^^^
error: text_signature not allowed with this method type error: text_signature not allowed with this method type
--> $DIR/invalid_pymethods.rs:73:24 --> $DIR/invalid_pymethods.rs:79:24
| |
73 | #[text_signature = "()"] 79 | #[text_signature = "()"]
| ^^^^ | ^^^^
error: cannot specify a second method type error: cannot specify a second method type
--> $DIR/invalid_pymethods.rs:80:7 --> $DIR/invalid_pymethods.rs:86:7
| |
80 | #[staticmethod] 86 | #[staticmethod]
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^
error: Python functions cannot have generic type parameters error: Python functions cannot have generic type parameters
--> $DIR/invalid_pymethods.rs:86:23 --> $DIR/invalid_pymethods.rs:92:23
| |
86 | fn generic_method<T>(value: T) {} 92 | fn generic_method<T>(value: T) {}
| ^ | ^
error: Python functions cannot have `impl Trait` arguments error: Python functions cannot have `impl Trait` arguments
--> $DIR/invalid_pymethods.rs:92:48 --> $DIR/invalid_pymethods.rs:98:48
| |
92 | fn impl_trait_method_first_arg(impl_trait: impl AsRef<PyAny>) {} 98 | fn impl_trait_method_first_arg(impl_trait: impl AsRef<PyAny>) {}
| ^^^^ | ^^^^
error: Python functions cannot have `impl Trait` arguments error: Python functions cannot have `impl Trait` arguments
--> $DIR/invalid_pymethods.rs:97:56 --> $DIR/invalid_pymethods.rs:103:56
| |
97 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {} 103 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
| ^^^^ | ^^^^