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 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 `#[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 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__`
- `PyNumberProtocol::__complex__` and `PyNumberProtocol::__round__`
- `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__`
- Deprecate `#[name = "..."]` attributes in favor of `#[pyo3(name = "...")]`. [#1567](https://github.com/PyO3/pyo3/pull/1567)
### Removed
- 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]
#[name = "solve"]
#[pyo3(name = "solve")]
pub fn solve_wrapper(model: &mut UserModel) {
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 quote::quote;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{parse_quote, Attribute, DataEnum, DeriveInput, Fields, Ident, Meta, MetaList, Result};
use syn::{
parenthesized,
parse::{Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
Attribute, DataEnum, DeriveInput, Fields, Ident, LitStr, Result, Token,
};
/// Describes derivation input of an enum.
#[derive(Debug)]
@ -26,7 +31,7 @@ impl<'a> Enum<'a> {
.variants
.iter()
.map(|variant| {
let attrs = ContainerAttribute::parse_attrs(&variant.attrs)?;
let attrs = ContainerOptions::from_attrs(&variant.attrs)?;
let var_ident = &variant.ident;
Container::new(
&variant.fields,
@ -86,7 +91,7 @@ enum ContainerType<'a> {
/// Struct Container, e.g. `struct Foo { a: String }`
///
/// 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 }`
///
/// The field specified by the identifier is extracted directly from the object.
@ -119,20 +124,20 @@ impl<'a> Container<'a> {
fn new(
fields: &'a Fields,
path: syn::Path,
attrs: Vec<ContainerAttribute>,
options: ContainerOptions,
is_enum_variant: bool,
) -> Result<Self> {
ensure_spanned!(
!fields.is_empty(),
fields.span() => "cannot derive FromPyObject for empty structs and variants"
);
let transparent = attrs
.iter()
.any(|attr| *attr == ContainerAttribute::Transparent);
if transparent {
Self::check_transparent_len(fields)?;
if options.transparent {
ensure_spanned!(
fields.len() == 1,
fields.span() => "transparent structs and variants can only have 1 field"
);
}
let style = match (fields, transparent) {
let style = match (fields, options.transparent) {
(Fields::Unnamed(_), true) => ContainerType::TupleNewtype,
(Fields::Unnamed(unnamed), false) => match unnamed.unnamed.len() {
1 => ContainerType::TupleNewtype,
@ -157,17 +162,17 @@ impl<'a> Container<'a> {
.ident
.as_ref()
.expect("Named fields should have identifiers");
let attrs = FieldAttributes::parse_attrs(&field.attrs)?;
let attrs = FieldPyO3Attributes::from_attrs(&field.attrs)?;
fields.push((ident, attrs))
}
ContainerType::Struct(fields)
}
(Fields::Unit, _) => unreachable!(), // covered by length check above
};
let err_name = attrs
.iter()
.find_map(|a| a.annotation().map(syn::LitStr::value))
.unwrap_or_else(|| path.segments.last().unwrap().ident.to_string());
let err_name = options.annotation.map_or_else(
|| path.segments.last().unwrap().ident.to_string(),
|lit_str| lit_str.value(),
);
let v = Container {
path,
@ -178,18 +183,6 @@ impl<'a> Container<'a> {
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.
fn build(&self) -> TokenStream {
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 mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new();
for (ident, attrs) in tups {
@ -256,67 +249,73 @@ impl<'a> Container<'a> {
}
quote!(Ok(#self_ty{#fields}))
}
}
fn check_transparent_len(fields: &Fields) -> Result<()> {
ensure_spanned!(
fields.len() == 1,
fields.span() => "transparent structs and variants can only have 1 field"
);
Ok(())
}
struct ContainerOptions {
transparent: bool,
annotation: Option<syn::LitStr>,
}
/// Attributes for deriving FromPyObject scoped on containers.
#[derive(Clone, Debug, PartialEq)]
enum ContainerAttribute {
enum ContainerPyO3Attribute {
/// 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.
ErrorAnnotation(syn::LitStr),
ErrorAnnotation(LitStr),
}
impl ContainerAttribute {
/// Convenience method to access `ErrorAnnotation`.
fn annotation(&self) -> Option<&syn::LitStr> {
match self {
ContainerAttribute::ErrorAnnotation(s) => Some(s),
_ => None,
impl Parse for ContainerPyO3Attribute {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::transparent) {
let kw: attributes::kw::transparent = input.parse()?;
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
///
/// Fails if any are invalid.
fn parse_attrs(value: &[Attribute]) -> Result<Vec<Self>> {
get_pyo3_meta_list(value)?
.nested
.into_iter()
.map(|meta| {
if let syn::NestedMeta::Meta(metaitem) = &meta {
match metaitem {
Meta::Path(p) if p.is_ident("transparent") => {
return Ok(ContainerAttribute::Transparent);
impl ContainerOptions {
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut options = ContainerOptions {
transparent: false,
annotation: None,
};
for attr in attrs {
if let Some(pyo3_attrs) = get_pyo3_attribute(attr)? {
for pyo3_attr in pyo3_attrs {
match pyo3_attr {
ContainerPyO3Attribute::Transparent(kw) => {
ensure_spanned!(
!options.transparent,
kw.span() => "`transparent` may only be provided once"
);
options.transparent = true;
}
Meta::NameValue(nv) if nv.path.is_ident("annotation") => {
if let syn::Lit::Str(s) = &nv.lit {
return Ok(ContainerAttribute::ErrorAnnotation(s.clone()));
} else {
bail_spanned!(nv.lit.span() => "expected string literal for annotation");
}
ContainerPyO3Attribute::ErrorAnnotation(lit_str) => {
ensure_spanned!(
options.annotation.is_none(),
lit_str.span() => "`annotation` may only be provided once"
);
options.annotation = Some(lit_str);
}
_ => {} // return Err below
}
}
bail_spanned!(meta.span() => "unknown `pyo3` container attribute");
})
.collect()
}
}
Ok(options)
}
}
/// Attributes for deriving FromPyObject scoped on fields.
#[derive(Clone, Debug)]
struct FieldAttributes {
struct FieldPyO3Attributes {
getter: FieldGetter,
from_py_with: Option<FromPyWithAttribute>,
}
@ -324,121 +323,96 @@ struct FieldAttributes {
#[derive(Clone, Debug)]
enum FieldGetter {
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.
///
fn parse_attrs(attrs: &[Attribute]) -> Result<Self> {
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut getter = None;
let mut from_py_with = None;
let list = get_pyo3_meta_list(attrs)?;
for meta_item in list.nested {
let meta = match meta_item {
syn::NestedMeta::Meta(meta) => meta,
syn::NestedMeta::Lit(lit) => bail_spanned!(
lit.span() =>
"expected `attribute`, `item` or `from_py_with`, got a literal"
),
};
let path = meta.path();
if path.is_ident("attribute") {
ensure_spanned!(
getter.is_none(),
meta.span() => "only one of `attribute` or `item` can be provided"
);
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`")
};
for attr in attrs {
if let Some(pyo3_attrs) = get_pyo3_attribute(attr)? {
for pyo3_attr in pyo3_attrs {
match pyo3_attr {
FieldPyO3Attribute::Getter(field_getter) => {
ensure_spanned!(
getter.is_none(),
attr.span() => "only one of `attribute` or `item` can be provided"
);
getter = Some(field_getter)
}
FieldPyO3Attribute::FromPyWith(from_py_with_attr) => {
ensure_spanned!(
from_py_with.is_none(),
attr.span() => "`from_py_with` may only be provided once"
);
from_py_with = Some(from_py_with_attr);
}
}
}
}
}
Ok(FieldAttributes {
Ok(FieldPyO3Attributes {
getter: getter.unwrap_or(FieldGetter::GetAttr(None)),
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>> {
@ -481,10 +455,12 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
en.build()
}
syn::Data::Struct(st) => {
let attrs = ContainerAttribute::parse_attrs(&tokens.attrs)?;
Container::verify_struct_container_attrs(&attrs)?;
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
if let Some(lit_str) = &options.annotation {
bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs");
}
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()
}
syn::Data::Union(_) => bail_spanned!(

View File

@ -1,34 +1,99 @@
use crate::pyfunction::parse_name_attribute;
use syn::ext::IdentExt;
use crate::attributes::{
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 is_class_attr: bool,
pub python_name: syn::Ident,
pub rust_ident: syn::Ident,
pub attributes: ConstAttributes,
}
impl ConstSpec {
// For now, the only valid attribute is `#[classattr]`.
pub fn parse(name: &syn::Ident, attrs: &mut Vec<syn::Attribute>) -> syn::Result<ConstSpec> {
let mut new_attrs = Vec::new();
let mut is_class_attr = false;
for attr in attrs.iter() {
if let syn::Meta::Path(name) = attr.parse_meta()? {
if name.is_ident("classattr") {
is_class_attr = true;
continue;
}
}
new_attrs.push(attr.clone());
/// Null-terminated Python name
pub fn python_name_with_deprecation(&self) -> TokenStream {
if let Some(name) = &self.attributes.name {
let deprecation =
utils::name_deprecation_token(name.0.span(), self.attributes.name_is_deprecated);
let name = format!("{}\0", name.0);
quote!({#deprecation #name})
} else {
let name = format!("{}\0", self.rust_ident.unraw().to_string());
quote!(#name)
}
attrs.clear();
attrs.extend(new_attrs);
Ok(ConstSpec {
is_class_attr,
python_name: parse_name_attribute(attrs)?.unwrap_or_else(|| name.unraw()),
})
}
}
pub struct ConstAttributes {
pub is_class_attr: bool,
pub name: Option<NameAttribute>,
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]
mod utils;
mod attrs;
mod attributes;
mod defs;
mod from_pyobject;
mod konst;
@ -22,9 +22,9 @@ mod pymethod;
mod pyproto;
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 pyfunction::{build_py_function, PyFunctionAttr};
pub use pyfunction::{build_py_function, PyFunctionOptions};
pub use pyimpl::{build_py_methods, PyClassMethodsType};
pub use pyproto::build_py_proto;
pub use utils::get_doc;

View File

@ -1,7 +1,8 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
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 proc_macro2::TokenStream;
use quote::ToTokens;
@ -17,7 +18,7 @@ pub struct FnArg<'a> {
pub ty: &'a syn::Type,
pub optional: Option<&'a syn::Type>,
pub py: bool,
pub attrs: PyFunctionArgAttrs,
pub attrs: PyFunctionArgPyO3Attributes,
}
impl<'a> FnArg<'a> {
@ -32,7 +33,7 @@ impl<'a> FnArg<'a> {
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 {
syn::Pat::Ident(syn::PatIdent {
ref ident,
@ -133,6 +134,7 @@ pub struct FnSpec<'a> {
pub args: Vec<FnArg<'a>>,
pub output: syn::Type,
pub doc: syn::LitStr,
pub name_is_deprecated: bool,
}
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
@ -161,13 +163,29 @@ impl<'a> FnSpec<'a> {
pub fn parse(
sig: &'a mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
allow_custom_name: bool,
options: PyFunctionOptions,
) -> syn::Result<FnSpec<'a>> {
let MethodAttributes {
ty: fn_type_attr,
args: fn_attrs,
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)?;
@ -199,9 +217,17 @@ impl<'a> FnSpec<'a> {
args: arguments,
output: ty,
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(
meth_attrs: &mut Vec<syn::Attribute>,
fn_type: &FnType,
@ -362,12 +388,11 @@ struct MethodAttributes {
fn parse_method_attributes(
attrs: &mut Vec<syn::Attribute>,
allow_custom_name: bool,
mut python_name: Option<syn::Ident>,
) -> syn::Result<MethodAttributes> {
let mut new_attrs = Vec::new();
let mut args = Vec::new();
let mut ty: Option<MethodTypeAttribute> = None;
let mut property_name = None;
macro_rules! set_ty {
($new_ty:expr, $ident:expr) => {
@ -434,7 +459,12 @@ fn parse_method_attributes(
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 => {
Some(w.segments[0].ident.clone())
}
@ -455,7 +485,7 @@ fn parse_method_attributes(
}
};
} else if path.is_ident("args") {
let attrs = PyFunctionAttr::from_meta(&nested)?;
let attrs = PyFunctionSignature::from_meta(&nested)?;
args.extend(attrs.arguments)
} else {
new_attrs.push(attr)
@ -467,21 +497,6 @@ fn parse_method_attributes(
*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 {
ty,
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";

View File

@ -1,13 +1,11 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
//! Code generation for the function that initializes a python module and adds classes and function.
use crate::method::{self, FnArg};
use crate::pyfunction::PyFunctionAttr;
use crate::pymethod::{check_generic, get_arg_names, impl_arg_params};
use crate::utils;
use crate::attributes::{attribute_ident_is, take_attributes, NameAttribute};
use crate::pyfunction::{impl_wrap_pyfunction, PyFunctionOptions};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{spanned::Spanned, Ident, Result};
use quote::quote;
use syn::{parse::Parse, spanned::Spanned, token::Comma, Ident, Path};
/// Generates the function that is called by the python interpreter to initialize the native
/// 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() {
if let syn::Stmt::Item(syn::Item::Fn(func)) = stmt {
if let Some((module_name, python_name, pyfn_attrs)) =
extract_pyfn_attrs(&mut func.attrs)?
{
let function_to_python = add_fn_to_module(func, python_name, pyfn_attrs)?;
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
if let Some(pyfn_args) = get_pyfn_attr(&mut func.attrs)? {
let module_name = pyfn_args.modname;
let (ident, wrapped_function) = impl_wrap_pyfunction(func, pyfn_args.options)?;
let item: syn::ItemFn = syn::parse_quote! {
fn block_wrapper() {
#function_to_python
#module_name.add_function(#function_wrapper_ident(#module_name)?)?;
#wrapped_function
#module_name.add_function(#ident(#module_name)?)?;
}
};
stmts.extend(item.block.stmts.into_iter());
@ -56,190 +52,49 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
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
fn extract_pyfn_attrs(
attrs: &mut Vec<syn::Attribute>,
) -> 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();
fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs>> {
let mut pyfn_args: Option<PyFnArgs> = None;
for attr in attrs.drain(..) {
match attr.parse_meta() {
Ok(syn::Meta::List(list)) if list.path.is_ident("pyfn") => {
let meta: Vec<_> = list.nested.iter().cloned().collect();
if meta.len() >= 2 {
// read module name
match &meta[0] {
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
modname = Some(path.clone())
}
_ => 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),
take_attributes(attrs, |attr| {
if attribute_ident_is(attr, "pyfn") {
ensure_spanned!(
pyfn_args.is_none(),
attr.span() => "`#[pyfn] may only be specified once"
);
pyfn_args = Some(attr.parse_args()?);
Ok(true)
} else {
Ok(false)
}
})?;
if let Some(pyfn_args) = &mut pyfn_args {
pyfn_args.options.take_pyo3_attributes(attrs)?;
}
*attrs = new_attrs;
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
})
}
})
Ok(pyfn_args)
}

View File

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

View File

@ -1,13 +1,22 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::attrs::FromPyWithAttribute;
use crate::module::add_fn_to_module;
use proc_macro2::TokenStream;
use syn::ext::IdentExt;
use syn::parse::ParseBuffer;
use crate::{
attributes::{
self, get_deprecated_name_attribute, get_pyo3_attribute, take_attributes,
FromPyWithAttribute, NameAttribute,
},
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::spanned::Spanned;
use syn::{NestedMeta, Path};
use syn::{ext::IdentExt, spanned::Spanned, Ident, NestedMeta, Path, Result};
use syn::{
parse::{Parse, ParseBuffer, ParseStream},
token::Comma,
};
#[derive(Debug, Clone, PartialEq)]
pub enum Argument {
@ -20,29 +29,69 @@ pub enum Argument {
/// The attributes of the pyfunction macro
#[derive(Default)]
pub struct PyFunctionAttr {
pub struct PyFunctionSignature {
pub arguments: Vec<Argument>,
has_kw: bool,
has_varargs: bool,
has_kwargs: bool,
pub pass_module: bool,
}
#[derive(Clone, PartialEq, Debug)]
pub struct PyFunctionArgAttrs {
pub struct PyFunctionArgPyO3Attributes {
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> {
let attr = Punctuated::<NestedMeta, syn::Token![,]>::parse_terminated(input)?;
Self::from_meta(&attr)
}
}
impl PyFunctionAttr {
impl PyFunctionSignature {
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 {
slf.add_item(item)?
@ -52,9 +101,6 @@ impl PyFunctionAttr {
pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> {
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::NameValue(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>> {
let mut name_attrs = Vec::new();
// Using retain will extract all name attributes from the attribute list
attrs.retain(|attr| match attr.parse_meta() {
Ok(syn::Meta::NameValue(nv)) if nv.path.is_ident("name") => {
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"
),
}
#[derive(Default)]
pub struct PyFunctionOptions {
pub pass_module: bool,
pub name: Option<NameAttribute>,
pub name_is_deprecated: bool,
pub signature: Option<PyFunctionSignature>,
}
pub fn build_py_function(ast: &mut syn::ItemFn, args: PyFunctionAttr) -> syn::Result<TokenStream> {
let python_name =
parse_name_attribute(&mut ast.attrs)?.unwrap_or_else(|| ast.sig.ident.unraw());
add_fn_to_module(ast, python_name, args)
}
impl Parse for PyFunctionOptions {
fn parse(input: ParseStream) -> Result<Self> {
let mut options = PyFunctionOptions {
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>> {
let mut new_attrs = Vec::new();
let mut metas = Vec::new();
for attr in attrs.drain(..) {
if let syn::Meta::List(meta_list) = attr.parse_meta()? {
if meta_list.path.is_ident("pyo3") {
for meta in meta_list.nested {
metas.push(meta);
while !input.is_empty() {
let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::name)
|| lookahead.peek(attributes::kw::pass_module)
|| lookahead.peek(attributes::kw::signature)
{
options.add_attributes(std::iter::once(input.parse()?))?;
if !input.is_empty() {
let _: Comma = input.parse()?;
}
} 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 {
/// Parses #[pyo3(from_python_with = "func")]
pub enum PyFunctionOption {
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> {
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)? {
let meta = match meta {
NestedMeta::Meta(meta) => meta,
NestedMeta::Lit(lit) => {
bail_spanned!(lit.span() => "expected `from_py_with`, got a literal")
}
};
if meta.path().is_ident("from_py_with") {
from_py_with = Some(FromPyWithAttribute::from_meta(meta)?);
pub fn take_pyo3_attributes(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
take_attributes(attrs, |attr| {
if let Some(pyo3_attributes) = get_pyo3_attribute(attr)? {
self.add_attributes(pyo3_attributes)?;
Ok(true)
} else if let Some(name) = get_deprecated_name_attribute(attr)? {
self.set_name(name)?;
self.name_is_deprecated = true;
Ok(true)
} 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(PyFunctionArgAttrs { from_py_with })
Ok(())
}
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)]
mod test {
use super::{Argument, PyFunctionAttr};
use super::{Argument, PyFunctionSignature};
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse_quote;
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)
}

View File

@ -1,6 +1,10 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::pymethod;
use crate::{
konst::{ConstAttributes, ConstSpec},
pyfunction::PyFunctionOptions,
pymethod,
};
use proc_macro2::TokenStream;
use pymethod::GeneratedPyMethod;
use quote::quote;
@ -39,7 +43,8 @@ pub fn impl_methods(
for iimpl in impls.iter_mut() {
match iimpl {
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) => {
let attrs = get_cfg_attributes(&meth.attrs);
methods.push(quote!(#(#attrs)* #token_stream));
@ -55,8 +60,14 @@ pub fn impl_methods(
}
}
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 meth = pymethod::gen_py_const(ty, &spec);
methods.push(quote!(#(#attrs)* #meth));
}
}

View File

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

View File

@ -3,6 +3,7 @@
use crate::defs;
use crate::method::{FnSpec, FnType};
use crate::proto_method::impl_method_proto;
use crate::pyfunction::PyFunctionOptions;
use crate::pymethod;
use proc_macro2::{Span, TokenStream};
use quote::quote;
@ -62,7 +63,8 @@ fn impl_proto_impl(
}
// Add non-slot methods to inventory like `#[pymethods]`
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 {
// 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
use proc_macro2::Span;
use proc_macro2::{Span, TokenStream};
use quote::quote_spanned;
use syn::spanned::Spanned;
/// 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))
}
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::{
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,
PyFunctionAttr,
PyFunctionOptions,
};
use quote::quote;
use syn::parse_macro_input;
@ -217,9 +217,9 @@ pub fn pymethods_with_inventory(_: TokenStream, input: TokenStream) -> TokenStre
#[proc_macro_attribute]
pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
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!(
#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 freelist;
mod gil;
pub mod impl_;
mod instance;
#[cfg(not(Py_LIMITED_API))]

View File

@ -19,13 +19,17 @@ impl Foo {
#[classattr]
const MY_CONST: &'static str = "foobar";
#[classattr]
#[pyo3(name = "RENAMED_CONST")]
const MY_CONST_2: &'static str = "foobar_2";
#[classattr]
fn a() -> i32 {
5
}
#[classattr]
#[name = "B"]
#[pyo3(name = "B")]
fn b() -> String {
"bar".to_string()
}
@ -46,9 +50,11 @@ fn class_attributes() {
let gil = Python::acquire_gil();
let py = gil.python();
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.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:

View File

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

View File

@ -34,6 +34,7 @@ fn test_compile_errors() {
#[rustversion::since(1.49)]
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_pymethod_receiver.rs");
t.compile_fail("tests/ui/pyclass_send.rs");

View File

@ -192,7 +192,7 @@ fn test_raw_idents() {
}
#[pyfunction]
#[name = "foobar"]
#[pyo3(name = "foobar")]
fn custom_named_fn() -> usize {
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
|
4 | fn invalid_attribute(#[pyo3(get)] param: String) {}
| ^^^
error: expected a name-value: `pyo3(from_py_with = "func")`
--> $DIR/invalid_argument_attributes.rs:7:33
error: expected `=`
--> $DIR/invalid_argument_attributes.rs:7:32
|
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
|
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
|
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 | | },
| |_____^
error: expected `attribute`, `item` or `from_py_with`
error: expected one of: `attribute`, `item`, `from_py_with`
--> $DIR/invalid_frompy_derive.rs:76:12
|
76 | #[pyo3(attr)]
| ^^^^
error: expected a single string literal argument
--> $DIR/invalid_frompy_derive.rs:82:12
error: expected string literal
--> $DIR/invalid_frompy_derive.rs:82:22
|
82 | #[pyo3(attribute(1))]
| ^^^^^^^^^
| ^
error: expected a single string literal argument
--> $DIR/invalid_frompy_derive.rs:88:12
error: expected at most one argument: `attribute` or `attribute("name")`
--> $DIR/invalid_frompy_derive.rs:88:25
|
88 | #[pyo3(attribute("a", "b"))]
| ^^^^^^^^^
| ^
error: attribute name cannot be empty
--> $DIR/invalid_frompy_derive.rs:94:22
@ -108,43 +108,43 @@ error: attribute name cannot be empty
94 | #[pyo3(attribute(""))]
| ^^
error: expected a single string literal argument
--> $DIR/invalid_frompy_derive.rs:100:12
error: unexpected end of input, expected string literal
--> $DIR/invalid_frompy_derive.rs:100:21
|
100 | #[pyo3(attribute())]
| ^^^^^^^^^
| ^^
error: expected a single literal argument
--> $DIR/invalid_frompy_derive.rs:106:12
error: expected at most one argument: `item` or `item(key)`
--> $DIR/invalid_frompy_derive.rs:106:20
|
106 | #[pyo3(item("a", "b"))]
| ^^^^
| ^
error: expected a single literal argument
--> $DIR/invalid_frompy_derive.rs:112:12
error: unexpected end of input, expected literal
--> $DIR/invalid_frompy_derive.rs:112:16
|
112 | #[pyo3(item())]
| ^^^^
| ^^
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)]
| ^^^^^^^^^
| ^
error: unknown `pyo3` container attribute
error: expected `transparent` or `annotation`
--> $DIR/invalid_frompy_derive.rs:123:8
|
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
|
129 | #[pyo3(annotation = "should not work")]
| ^^^^^^^^^^^^^^^^^
error: expected string literal for annotation
error: expected string literal
--> $DIR/invalid_frompy_derive.rs:136:25
|
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)
error: expected a name-value: `pyo3(from_py_with = "func")`
--> $DIR/invalid_frompy_derive.rs:158:12
error: expected `=`
--> $DIR/invalid_frompy_derive.rs:158:11
|
158 | #[pyo3(from_py_with)]
| ^^^^^^^^^^^^
| ^^^^^^^^^^^^^^
error: expected literal
error: expected string literal
--> $DIR/invalid_frompy_derive.rs:164:27
|
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) {}
| ^^^
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
|
28 | struct TupleGetterSetter(#[pyo3(get, set)] i32);

View File

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

View File

@ -1,17 +1,17 @@
error: name cannot be specified twice
--> $DIR/invalid_pymethod_names.rs:10:5
error: `name` may only be specified once
--> $DIR/invalid_pymethod_names.rs:10:19
|
10 | #[name = "num"]
| ^
10 | #[pyo3(name = "num")]
| ^^^^^
error: #[name] can not be specified multiple times
--> $DIR/invalid_pymethod_names.rs:18:5
error: `name` may only be specified once
--> $DIR/invalid_pymethod_names.rs:18:19
|
18 | #[name = "bar"]
| ^
18 | #[pyo3(name = "bar")]
| ^^^^^
error: name not allowed with this method type
--> $DIR/invalid_pymethod_names.rs:24:5
error: `name` not allowed with `#[new]`
--> $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) {}
}
#[pymethods]
impl MyClass {
#[classattr(foobar)]
const CLASS_ATTR_WITH_ATTRIBUTE_ARG: i32 = 3;
}
#[pymethods]
impl MyClass {
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) {}
| ^^^
error: static method needs #[staticmethod] attribute
error: `#[classattr]` does not take any arguments
--> $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
--> $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]
--> $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]
--> $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
--> $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
--> $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
--> $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
--> $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
--> $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
--> $DIR/invalid_pymethods.rs:80:7
--> $DIR/invalid_pymethods.rs:86:7
|
80 | #[staticmethod]
86 | #[staticmethod]
| ^^^^^^^^^^^^
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
--> $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
--> $DIR/invalid_pymethods.rs:97:56
|
97 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
| ^^^^
--> $DIR/invalid_pymethods.rs:103:56
|
103 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
| ^^^^