diff --git a/CHANGELOG.md b/CHANGELOG.md index 48c65c6eeb2..41940debbc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__` - Deprecate `#[name = "..."]` attributes in favor of `#[pyo3(name = "...")]`. [#1567](https://github.com/PyO3/pyo3/pull/1567) - Improve compilation times for projects using PyO3 [#1604](https://github.com/PyO3/pyo3/pull/1604) +- Deprecate string-literal second argument to `#[pyfn(m, "name")]`. ### Removed - Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426) diff --git a/guide/src/class/protocols.md b/guide/src/class/protocols.md index 83ab8c592a9..8f08548f6cc 100644 --- a/guide/src/class/protocols.md +++ b/guide/src/class/protocols.md @@ -242,8 +242,6 @@ These correspond to the slots `tp_traverse` and `tp_clear` in the Python C API. as every cycle must contain at least one mutable reference. Example: ```rust -extern crate pyo3; - use pyo3::prelude::*; use pyo3::PyTraverseError; use pyo3::gc::{PyGCProtocol, PyVisit}; diff --git a/guide/src/function.md b/guide/src/function.md index a6e5b57d29f..1267fad5dc5 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -1,9 +1,28 @@ # Python Functions -PyO3 supports two ways to define a free function in Python. Both require registering -the function to a [module](./module.md). +PyO3 supports two ways to define a free function in Python. Both require registering the function to a [module](./module.md). -One way is defining the function in the module definition, annotated with `#[pyfn]`. +One way is annotating a function with `#[pyfunction]` and then adding it to the module using the `wrap_pyfunction!` macro. + +```rust +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +#[pyfunction] +fn double(x: usize) -> usize { + x * 2 +} + +#[pymodule] +fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(double, m)?)?; + Ok(()) +} + +# fn main() {} +``` + +Alternatively there is a shorthand; the function can be placed inside the module definition and annotated with `#[pyfn]`, as below: ```rust use pyo3::prelude::*; @@ -11,8 +30,8 @@ use pyo3::prelude::*; #[pymodule] fn rust2py(py: Python, m: &PyModule) -> PyResult<()> { - #[pyfn(m, "sum_as_string")] - fn sum_as_string_py(_py: Python, a:i64, b:i64) -> PyResult { + #[pyfn(m)] + fn sum_as_string(_py: Python, a:i64, b:i64) -> PyResult { Ok(format!("{}", a + b)) } @@ -22,21 +41,20 @@ fn rust2py(py: Python, m: &PyModule) -> PyResult<()> { # fn main() {} ``` -The other is annotating a function with `#[pyfunction]` and then adding it -to the module using the `wrap_pyfunction!` macro. +`#[pyfn(m)]` is just syntax sugar for `#[pyfunction]`, and takes all the same options documented in the rest of this chapter. The code above is expanded to the following: ```rust use pyo3::prelude::*; -use pyo3::wrap_pyfunction; - -#[pyfunction] -fn double(x: usize) -> usize { - x * 2 -} #[pymodule] -fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(double, m)?).unwrap(); +fn rust2py(py: Python, m: &PyModule) -> PyResult<()> { + + #[pyfunction] + fn sum_as_string(_py: Python, a:i64, b:i64) -> PyResult { + Ok(format!("{}", a + b)) + } + + m.add_function(pyo3::wrap_pyfunction!(sum_as_string, m)?)?; Ok(()) } @@ -44,16 +62,40 @@ fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { # fn main() {} ``` +## Function options + +The `#[pyo3]` attribute can be used to modify properties of the generated Python function. It can take any combination of the following options: + + - `#[pyo3(name = "...")]` + + Overrides the name generated in Python: + + ```rust + use pyo3::prelude::*; + use pyo3::wrap_pyfunction; + + #[pyfunction] + #[pyo3(name = "no_args")] + fn no_args_py() -> usize { 42 } + + #[pymodule] + fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(no_args_py, m)?)?; + Ok(()) + } + + # Python::with_gil(|py| { + # let m = pyo3::wrap_pymodule!(module_with_functions)(py); + # assert!(m.getattr(py, "no_args").is_ok()); + # assert!(m.getattr(py, "no_args_py").is_err()); + # }); + ``` + ## Argument parsing -Both the `#[pyfunction]` and `#[pyfn]` attributes support specifying details of -argument parsing. The details are given in the section "Method arguments" in -the [Classes](class.md) chapter. Here is an example for a function that accepts -arbitrary keyword arguments (`**kwargs` in Python syntax) and returns the number -that was passed: +The `#[pyfunction]` attribute supports specifying details of argument parsing. The details are given in the section ["Method arguments" of the Classes chapter](class.md#method-arguments). Here is an example for a function that accepts arbitrary keyword arguments (`**kwargs` in Python syntax) and returns the number that was passed: ```rust -# extern crate pyo3; use pyo3::prelude::*; use pyo3::wrap_pyfunction; use pyo3::types::PyDict; @@ -213,8 +255,7 @@ in Python code. ### Accessing the module of a function -It is possible to access the module of a `#[pyfunction]` and `#[pyfn]` in the -function body by passing the `pass_module` argument to the attribute: +It is possible to access the module of a `#[pyfunction]` in the function body by passing the `pass_module` argument to the attribute: ```rust use pyo3::wrap_pyfunction; @@ -236,25 +277,6 @@ fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> { If `pass_module` is set, the first argument **must** be the `&PyModule`. It is then possible to use the module in the function body. -The same works for `#[pyfn]`: - -```rust -use pyo3::wrap_pyfunction; -use pyo3::prelude::*; - -#[pymodule] -fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> { - - #[pyfn(m, "module_name", pass_module)] - fn module_name(module: &PyModule) -> PyResult<&str> { - module.name() - } - Ok(()) -} - -# fn main() {} -``` - ## Accessing the FFI functions In order to make Rust functions callable from Python, PyO3 generates a diff --git a/guide/src/module.md b/guide/src/module.md index aabba50b392..9f97d972a53 100644 --- a/guide/src/module.md +++ b/guide/src/module.md @@ -15,7 +15,8 @@ fn rust2py(py: Python, m: &PyModule) -> PyResult<()> { // Note that the `#[pyfn()]` annotation automatically converts the arguments from // Python objects to Rust values, and the Rust return value back into a Python object. // The `_py` argument represents that we're holding the GIL. - #[pyfn(m, "sum_as_string")] + #[pyfn(m)] + #[pyo3(name = "sum_as_string")] fn sum_as_string_py(_py: Python, a: i64, b: i64) -> PyResult { let out = sum_as_string(a, b); Ok(out) @@ -32,7 +33,7 @@ fn sum_as_string(a: i64, b: i64) -> String { # fn main() {} ``` -The `#[pymodule]` procedural macro attribute takes care of exporting the initialization function of your +The `#[pymodule]` procedural macro attribute takes care of exporting the initialization function of your module to Python. It can take as an argument the name of your module, which must be the name of the `.so` or `.pyd` file; the default is the Rust function's name. @@ -41,7 +42,7 @@ If the name of the module (the default being the function name) does not match t `ImportError: dynamic module does not define module export function (PyInit_name_of_your_module)` To import the module, either copy the shared library as described in [the README](https://github.com/PyO3/pyo3) -or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or +or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or `python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust). ## Documentation @@ -95,4 +96,4 @@ fn supermodule(py: Python, module: &PyModule) -> PyResult<()> { This way, you can create a module hierarchy within a single extension module. -It is not necessary to add `#[pymodule]` on nested modules, this is only required on the top-level module. \ No newline at end of file +It is not necessary to add `#[pymodule]` on nested modules, this is only required on the top-level module. diff --git a/pyo3-macros-backend/src/attributes.rs b/pyo3-macros-backend/src/attributes.rs index a347e18d5f3..709d13a4b77 100644 --- a/pyo3-macros-backend/src/attributes.rs +++ b/pyo3-macros-backend/src/attributes.rs @@ -6,6 +6,8 @@ use syn::{ Attribute, ExprPath, Ident, LitStr, Result, Token, }; +use crate::deprecations::{Deprecation, Deprecations}; + pub mod kw { syn::custom_keyword!(annotation); syn::custom_keyword!(attribute); @@ -81,17 +83,18 @@ pub fn take_attributes( Ok(()) } -pub fn get_deprecated_name_attribute(attr: &syn::Attribute) -> syn::Result> { +pub fn get_deprecated_name_attribute( + attr: &syn::Attribute, + deprecations: &mut Deprecations, +) -> syn::Result> { 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))) + deprecations.push(Deprecation::NameAttribute, attr.span()); + Ok(Some(NameAttribute(s.parse()?))) } _ => Ok(None), } diff --git a/pyo3-macros-backend/src/deprecations.rs b/pyo3-macros-backend/src/deprecations.rs new file mode 100644 index 00000000000..44c100df324 --- /dev/null +++ b/pyo3-macros-backend/src/deprecations.rs @@ -0,0 +1,43 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{quote_spanned, ToTokens}; + +pub enum Deprecation { + NameAttribute, + PyfnNameArgument, +} + +impl Deprecation { + fn ident(&self, span: Span) -> syn::Ident { + let string = match self { + Deprecation::NameAttribute => "NAME_ATTRIBUTE", + Deprecation::PyfnNameArgument => "PYFN_NAME_ARGUMENT", + }; + syn::Ident::new(string, span) + } +} + +#[derive(Default)] +pub struct Deprecations(Vec<(Deprecation, Span)>); + +impl Deprecations { + pub fn new() -> Self { + Deprecations(Vec::new()) + } + + pub fn push(&mut self, deprecation: Deprecation, span: Span) { + self.0.push((deprecation, span)) + } +} + +impl ToTokens for Deprecations { + fn to_tokens(&self, tokens: &mut TokenStream) { + for (deprecation, span) in &self.0 { + let ident = deprecation.ident(*span); + quote_spanned!( + *span => + let _ = pyo3::impl_::deprecations::#ident; + ) + .to_tokens(tokens) + } + } +} diff --git a/pyo3-macros-backend/src/konst.rs b/pyo3-macros-backend/src/konst.rs index 7a9c7f0c765..1e9203bc62a 100644 --- a/pyo3-macros-backend/src/konst.rs +++ b/pyo3-macros-backend/src/konst.rs @@ -1,8 +1,10 @@ -use crate::attributes::{ - self, get_deprecated_name_attribute, get_pyo3_attributes, is_attribute_ident, take_attributes, - NameAttribute, +use crate::{ + attributes::{ + self, get_deprecated_name_attribute, get_pyo3_attributes, is_attribute_ident, + take_attributes, NameAttribute, + }, + deprecations::Deprecations, }; -use crate::utils; use proc_macro2::TokenStream; use quote::quote; use syn::{ @@ -19,12 +21,10 @@ pub struct ConstSpec { impl ConstSpec { /// Null-terminated Python name - pub fn python_name_with_deprecation(&self) -> TokenStream { + pub fn null_terminated_python_name(&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}) + quote!({#name}) } else { let name = format!("{}\0", self.rust_ident.unraw().to_string()); quote!(#name) @@ -35,7 +35,7 @@ impl ConstSpec { pub struct ConstAttributes { pub is_class_attr: bool, pub name: Option, - pub name_is_deprecated: bool, + pub deprecations: Deprecations, } pub enum PyO3ConstAttribute { @@ -58,7 +58,7 @@ impl ConstAttributes { let mut attributes = ConstAttributes { is_class_attr: false, name: None, - name_is_deprecated: false, + deprecations: Deprecations::new(), }; take_attributes(attrs, |attr| { @@ -76,9 +76,10 @@ impl ConstAttributes { } } Ok(true) - } else if let Some(name) = get_deprecated_name_attribute(attr)? { + } else if let Some(name) = + get_deprecated_name_attribute(attr, &mut attributes.deprecations)? + { attributes.set_name(name)?; - attributes.name_is_deprecated = true; Ok(true) } else { Ok(false) diff --git a/pyo3-macros-backend/src/lib.rs b/pyo3-macros-backend/src/lib.rs index 5985b396254..b8ae6866fe7 100644 --- a/pyo3-macros-backend/src/lib.rs +++ b/pyo3-macros-backend/src/lib.rs @@ -10,6 +10,7 @@ mod utils; mod attributes; mod defs; +mod deprecations; mod from_pyobject; mod konst; mod method; diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index b51212e62bb..36dbfc5fa74 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -1,9 +1,9 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use crate::pyfunction::Argument; use crate::pyfunction::PyFunctionOptions; use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature}; use crate::utils; +use crate::{deprecations::Deprecations, pyfunction::Argument}; use proc_macro2::TokenStream; use quote::ToTokens; use quote::{quote, quote_spanned}; @@ -122,7 +122,6 @@ impl SelfType { } } -#[derive(Clone, Debug)] pub struct FnSpec<'a> { pub tp: FnType, // Rust function name @@ -134,7 +133,7 @@ pub struct FnSpec<'a> { pub args: Vec>, pub output: syn::Type, pub doc: syn::LitStr, - pub name_is_deprecated: bool, + pub deprecations: Deprecations, } pub fn get_return_info(output: &syn::ReturnType) -> syn::Type { @@ -217,15 +216,13 @@ impl<'a> FnSpec<'a> { args: arguments, output: ty, doc, - name_is_deprecated: options.name_is_deprecated, + deprecations: options.deprecations, }) } - pub fn python_name_with_deprecation(&self) -> TokenStream { - let deprecation = - utils::name_deprecation_token(self.python_name.span(), self.name_is_deprecated); + pub fn null_terminated_python_name(&self) -> TokenStream { let name = format!("{}\0", self.python_name); - quote!({#deprecation #name}) + quote!({#name}) } fn parse_text_signature( diff --git a/pyo3-macros-backend/src/module.rs b/pyo3-macros-backend/src/module.rs index c0ca8c7fe76..5bc8962e5df 100644 --- a/pyo3-macros-backend/src/module.rs +++ b/pyo3-macros-backend/src/module.rs @@ -1,8 +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::attributes::{is_attribute_ident, take_attributes, NameAttribute}; use crate::pyfunction::{impl_wrap_pyfunction, PyFunctionOptions}; +use crate::{ + attributes::{is_attribute_ident, take_attributes, NameAttribute}, + deprecations::Deprecation, +}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{parse::Parse, spanned::Spanned, token::Comma, Ident, Path}; @@ -59,18 +62,35 @@ pub struct PyFnArgs { impl Parse for PyFnArgs { fn parse(input: syn::parse::ParseStream) -> syn::Result { - let modname = input.parse()?; - let _: Comma = input.parse()?; - let fnname_literal: syn::LitStr = input.parse()?; - let fnname = fnname_literal.parse()?; + let modname = input.parse().map_err( + |e| err_spanned!(e.span() => "expected module as first argument to #[pyfn()]"), + )?; + if input.is_empty() { - let mut options = PyFunctionOptions::default(); - options.set_name(NameAttribute(fnname))?; - return Ok(Self { modname, options }); + return Ok(Self { + modname, + options: Default::default(), + }); } + let _: Comma = input.parse()?; + + let mut deprecated_name_argument = None; + if let Ok(lit_str) = input.parse::() { + deprecated_name_argument = Some(lit_str); + if !input.is_empty() { + let _: Comma = input.parse()?; + } + } + let mut options: PyFunctionOptions = input.parse()?; - options.set_name(NameAttribute(fnname))?; + if let Some(lit_str) = deprecated_name_argument { + options.set_name(NameAttribute(lit_str.parse()?))?; + options + .deprecations + .push(Deprecation::PyfnNameArgument, lit_str.span()); + } + Ok(Self { modname, options }) } } diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 0a5ab8c1cde..9b3d62a38c1 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -508,10 +508,10 @@ fn impl_descriptors( ); match desc { FnType::Getter(self_ty) => { - impl_py_getter_def(cls, property_type, self_ty, &doc) + impl_py_getter_def(cls, property_type, self_ty, &doc, &Default::default()) } FnType::Setter(self_ty) => { - impl_py_setter_def(cls, property_type, self_ty, &doc) + impl_py_setter_def(cls, property_type, self_ty, &doc, &Default::default()) } _ => unreachable!(), } diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index d1767aec86c..e61eebefbfa 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -5,6 +5,7 @@ use crate::{ self, get_deprecated_name_attribute, get_pyo3_attributes, take_attributes, FromPyWithAttribute, NameAttribute, }, + deprecations::Deprecations, method::{self, FnArg, FnSpec}, pymethod::{check_generic, get_arg_names, impl_arg_params}, utils, @@ -209,8 +210,8 @@ impl PyFunctionSignature { pub struct PyFunctionOptions { pub pass_module: bool, pub name: Option, - pub name_is_deprecated: bool, pub signature: Option, + pub deprecations: Deprecations, } impl Parse for PyFunctionOptions { @@ -218,8 +219,8 @@ impl Parse for PyFunctionOptions { let mut options = PyFunctionOptions { pass_module: false, name: None, - name_is_deprecated: false, signature: None, + deprecations: Deprecations::new(), }; while !input.is_empty() { @@ -278,9 +279,9 @@ impl PyFunctionOptions { if let Some(pyo3_attributes) = get_pyo3_attributes(attr)? { self.add_attributes(pyo3_attributes)?; Ok(true) - } else if let Some(name) = get_deprecated_name_attribute(attr)? { + } else if let Some(name) = get_deprecated_name_attribute(attr, &mut self.deprecations)? + { self.set_name(name)?; - self.name_is_deprecated = true; Ok(true) } else { Ok(false) @@ -390,11 +391,11 @@ pub fn impl_wrap_pyfunction( args: arguments, output: ty, doc, - name_is_deprecated: options.name_is_deprecated, + deprecations: options.deprecations, }; let doc = &spec.doc; - let python_name = spec.python_name_with_deprecation(); + let python_name = spec.null_terminated_python_name(); let name = &func.sig.ident; let wrapper_ident = format_ident!("__pyo3_raw_{}", name); @@ -453,12 +454,14 @@ fn function_c_wrapper( ) }; let py = syn::Ident::new("_py", Span::call_site()); + let deprecations = &spec.deprecations; if spec.args.is_empty() { Ok(quote! { unsafe extern "C" fn #wrapper_ident( _slf: *mut pyo3::ffi::PyObject, _unused: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject { + #deprecations pyo3::callback::handle_panic(|#py| { #slf_module #cb @@ -473,6 +476,7 @@ fn function_c_wrapper( _args: *mut pyo3::ffi::PyObject, _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject { + #deprecations pyo3::callback::handle_panic(|#py| { #slf_module let _args = #py.from_borrowed_ptr::(_args); diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index e27a1d04f6d..ebb85aabcd6 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -1,6 +1,6 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use crate::utils; use crate::{attributes::FromPyWithAttribute, konst::ConstSpec}; +use crate::{deprecations::Deprecations, utils}; use crate::{ method::{FnArg, FnSpec, FnType, SelfType}, pyfunction::PyFunctionOptions, @@ -48,12 +48,14 @@ pub fn gen_py_method( PropertyType::Function(&spec), self_ty, &spec.doc, + &spec.deprecations, )?), FnType::Setter(self_ty) => GeneratedPyMethod::Method(impl_py_setter_def( cls, PropertyType::Function(&spec), self_ty, &spec.doc, + &spec.deprecations, )?), }) } @@ -72,8 +74,10 @@ pub(crate) fn check_generic(sig: &syn::Signature) -> syn::Result<()> { pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream { let member = &spec.rust_ident; + let deprecations = &spec.attributes.deprecations; let wrapper = quote! {{ fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject { + #deprecations pyo3::IntoPy::into_py(#cls::#member, py) } __wrap @@ -91,12 +95,14 @@ pub fn impl_wrap_cfunction_with_keywords( let slf = self_ty.receiver(cls); let py = syn::Ident::new("_py", Span::call_site()); let body = impl_arg_params(&spec, Some(cls), body, &py)?; + let deprecations = &spec.deprecations; Ok(quote! {{ unsafe extern "C" fn __wrap( _slf: *mut pyo3::ffi::PyObject, _args: *mut pyo3::ffi::PyObject, _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject { + #deprecations pyo3::callback::handle_panic(|#py| { #slf let _args = #py.from_borrowed_ptr::(_args); @@ -113,6 +119,7 @@ pub fn impl_wrap_cfunction_with_keywords( pub fn impl_wrap_noargs(cls: &syn::Type, spec: &FnSpec<'_>, self_ty: &SelfType) -> TokenStream { let body = impl_call(cls, &spec); let slf = self_ty.receiver(cls); + let deprecations = &spec.deprecations; assert!(spec.args.is_empty()); quote! {{ unsafe extern "C" fn __wrap( @@ -120,6 +127,7 @@ pub fn impl_wrap_noargs(cls: &syn::Type, spec: &FnSpec<'_>, self_ty: &SelfType) _args: *mut pyo3::ffi::PyObject, ) -> *mut pyo3::ffi::PyObject { + #deprecations pyo3::callback::handle_panic(|_py| { #slf #body @@ -136,7 +144,7 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result let cb = quote! { #cls::#name(#(#names),*) }; let py = syn::Ident::new("_py", Span::call_site()); let body = impl_arg_params(spec, Some(cls), cb, &py)?; - + let deprecations = &spec.deprecations; Ok(quote! {{ #[allow(unused_mut)] unsafe extern "C" fn __wrap( @@ -144,8 +152,8 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result _args: *mut pyo3::ffi::PyObject, _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject { + #deprecations use pyo3::callback::IntoPyCallbackOutput; - pyo3::callback::handle_panic(|#py| { let _args = #py.from_borrowed_ptr::(_args); let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs); @@ -166,7 +174,7 @@ pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> Result) -> Result *mut pyo3::ffi::PyObject { + #deprecations pyo3::callback::handle_panic(|#py| { let _cls = pyo3::types::PyType::from_type_ptr(#py, _cls as *mut pyo3::ffi::PyTypeObject); let _args = #py.from_borrowed_ptr::(_args); @@ -193,7 +202,7 @@ pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> Result) -> Result *mut pyo3::ffi::PyObject { + #deprecations pyo3::callback::handle_panic(|#py| { let _args = #py.from_borrowed_ptr::(_args); let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs); @@ -218,9 +228,10 @@ pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> Result) -> TokenStream { let name = &spec.name; let cb = quote! { #cls::#name() }; - + let deprecations = &spec.deprecations; quote! {{ fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject { + #deprecations pyo3::IntoPy::into_py(#cb, py) } __wrap @@ -578,7 +589,7 @@ pub fn impl_py_method_def( flags: Option, ) -> Result { let add_flags = flags.map(|flags| quote!(.flags(#flags))); - let python_name = spec.python_name_with_deprecation(); + let python_name = spec.null_terminated_python_name(); let doc = &spec.doc; if spec.args.is_empty() { let wrapper = impl_wrap_noargs(cls, spec, self_ty); @@ -621,7 +632,7 @@ pub fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result Result { let wrapper = impl_wrap_class(cls, &spec)?; - let python_name = spec.python_name_with_deprecation(); + let python_name = spec.null_terminated_python_name(); let doc = &spec.doc; Ok(quote! { pyo3::class::PyMethodDefType::Class({ @@ -636,7 +647,7 @@ pub fn impl_py_method_def_class(cls: &syn::Type, spec: &FnSpec) -> Result Result { let wrapper = impl_wrap_static(cls, &spec)?; - let python_name = spec.python_name_with_deprecation(); + let python_name = spec.null_terminated_python_name(); let doc = &spec.doc; Ok(quote! { pyo3::class::PyMethodDefType::Static({ @@ -651,7 +662,7 @@ pub fn impl_py_method_def_static(cls: &syn::Type, spec: &FnSpec) -> Result TokenStream { let wrapper = impl_wrap_class_attribute(cls, &spec); - let python_name = spec.python_name_with_deprecation(); + let python_name = spec.null_terminated_python_name(); quote! { pyo3::class::PyMethodDefType::ClassAttribute({ pyo3::class::PyClassAttributeDef::new( @@ -663,7 +674,7 @@ 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_with_deprecation(); + let python_name = &spec.null_terminated_python_name(); quote! { { pyo3::class::PyMethodDefType::ClassAttribute({ @@ -696,17 +707,19 @@ pub(crate) fn impl_py_setter_def( property_type: PropertyType, self_ty: &SelfType, doc: &syn::LitStr, + deprecations: &Deprecations, ) -> Result { 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(), + PropertyType::Function(spec) => spec.null_terminated_python_name(), }; let wrapper = impl_wrap_setter(cls, property_type, self_ty)?; Ok(quote! { pyo3::class::PyMethodDefType::Setter({ + #deprecations pyo3::class::PySetterDef::new( #python_name, pyo3::class::methods::PySetter(#wrapper), @@ -721,17 +734,19 @@ pub(crate) fn impl_py_getter_def( property_type: PropertyType, self_ty: &SelfType, doc: &syn::LitStr, + deprecations: &Deprecations, ) -> Result { 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(), + PropertyType::Function(spec) => spec.null_terminated_python_name(), }; let wrapper = impl_wrap_getter(cls, property_type, self_ty)?; Ok(quote! { pyo3::class::PyMethodDefType::Getter({ + #deprecations pyo3::class::PyGetterDef::new( #python_name, pyo3::class::methods::PyGetter(#wrapper), diff --git a/pyo3-macros-backend/src/utils.rs b/pyo3-macros-backend/src/utils.rs index db2e96a6448..991361c98cf 100644 --- a/pyo3-macros-backend/src/utils.rs +++ b/pyo3-macros-backend/src/utils.rs @@ -1,6 +1,5 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use proc_macro2::{Span, TokenStream}; -use quote::quote_spanned; +use proc_macro2::Span; use syn::spanned::Spanned; /// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span. @@ -165,14 +164,3 @@ pub fn get_doc( Ok(syn::LitStr::new(&doc, span)) } - -pub fn name_deprecation_token(span: Span, name_is_deprecated: bool) -> Option { - if name_is_deprecated { - Some(quote_spanned!( - span => - let _ = pyo3::impl_::deprecations::NAME_ATTRIBUTE; - )) - } else { - None - } -} diff --git a/src/impl_/mod.rs b/src/impl_.rs similarity index 51% rename from src/impl_/mod.rs rename to src/impl_.rs index 5cb3547588b..f8ef67919e9 100644 --- a/src/impl_/mod.rs +++ b/src/impl_.rs @@ -2,12 +2,4 @@ //! 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: () = (); -} +pub mod deprecations; diff --git a/src/impl_/deprecations.rs b/src/impl_/deprecations.rs new file mode 100644 index 00000000000..94707455f05 --- /dev/null +++ b/src/impl_/deprecations.rs @@ -0,0 +1,13 @@ +//! Symbols used to denote deprecated usages of PyO3's proc macros. + +#[deprecated( + since = "0.14.0", + note = "use `#[pyo3(name = \"...\")]` instead of `#[name = \"...\"]`" +)] +pub const NAME_ATTRIBUTE: () = (); + +#[deprecated( + since = "0.14.0", + note = "use `#[pyfn(m)] #[pyo3(name = \"...\")]` instead of `#[pyfn(m, \"...\")]`" +)] +pub const PYFN_NAME_ARGUMENT: () = (); diff --git a/tests/test_module.rs b/tests/test_module.rs index 9267ebb6b39..74a34dd9731 100644 --- a/tests/test_module.rs +++ b/tests/test_module.rs @@ -1,7 +1,7 @@ use pyo3::prelude::*; -use pyo3::py_run; use pyo3::types::{IntoPyDict, PyDict, PyTuple}; +use pyo3::{py_run, wrap_pyfunction}; mod common; #[pyclass] @@ -36,24 +36,24 @@ fn double(x: usize) -> usize { /// This module is implemented in Rust. #[pymodule] fn module_with_functions(_py: Python, m: &PyModule) -> PyResult<()> { - use pyo3::wrap_pyfunction; - + #![allow(deprecated)] #[pyfn(m, "sum_as_string")] - fn sum_as_string_py(_py: Python, a: i64, b: i64) -> String { + fn function_with_deprecated_name(_py: Python, a: i64, b: i64) -> String { sum_as_string(a, b) } - #[pyfn(m, "no_parameters")] - fn no_parameters() -> usize { + #[pyfn(m)] + #[pyo3(name = "no_parameters")] + fn function_with_name() -> usize { 42 } - #[pyfn(m, "with_module", pass_module)] + #[pyfn(m, pass_module)] fn with_module(module: &PyModule) -> PyResult<&str> { module.name() } - #[pyfn(m, "double_value")] + #[pyfn(m)] fn double_value(v: &ValueClass) -> usize { v.value * 2 } @@ -174,8 +174,6 @@ fn r#move() -> usize { #[pymodule] fn raw_ident_module(_py: Python, module: &PyModule) -> PyResult<()> { - use pyo3::wrap_pyfunction; - module.add_function(wrap_pyfunction!(r#move, module)?) } @@ -199,8 +197,6 @@ fn custom_named_fn() -> usize { #[pymodule] fn foobar_module(_py: Python, m: &PyModule) -> PyResult<()> { - use pyo3::wrap_pyfunction; - m.add_function(wrap_pyfunction!(custom_named_fn, m)?)?; m.dict().set_item("yay", "me")?; Ok(()) @@ -241,16 +237,12 @@ fn subfunction() -> String { } fn submodule(module: &PyModule) -> PyResult<()> { - use pyo3::wrap_pyfunction; - module.add_function(wrap_pyfunction!(subfunction, module)?)?; Ok(()) } #[pymodule] fn submodule_with_init_fn(_py: Python, module: &PyModule) -> PyResult<()> { - use pyo3::wrap_pyfunction; - module.add_function(wrap_pyfunction!(subfunction, module)?)?; Ok(()) } @@ -262,8 +254,6 @@ fn superfunction() -> String { #[pymodule] fn supermodule(py: Python, module: &PyModule) -> PyResult<()> { - use pyo3::wrap_pyfunction; - module.add_function(wrap_pyfunction!(superfunction, module)?)?; let module_to_add = PyModule::new(py, "submodule")?; submodule(module_to_add)?; @@ -308,12 +298,12 @@ fn ext_vararg_fn(py: Python, a: i32, vararg: &PyTuple) -> PyObject { #[pymodule] fn vararg_module(_py: Python, m: &PyModule) -> PyResult<()> { - #[pyfn(m, "int_vararg_fn", a = 5, vararg = "*")] + #[pyfn(m, a = 5, vararg = "*")] fn int_vararg_fn(py: Python, a: i32, vararg: &PyTuple) -> PyObject { ext_vararg_fn(py, a, vararg) } - m.add_function(pyo3::wrap_pyfunction!(ext_vararg_fn, m)?) + m.add_function(wrap_pyfunction!(ext_vararg_fn, m)?) .unwrap(); Ok(()) } @@ -390,14 +380,14 @@ fn pyfunction_with_module_and_args_kwargs<'a>( #[pymodule] fn module_with_functions_with_module(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module_and_py, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module_and_arg, m)?)?; - m.add_function(pyo3::wrap_pyfunction!( + m.add_function(wrap_pyfunction!(pyfunction_with_module, m)?)?; + m.add_function(wrap_pyfunction!(pyfunction_with_module_and_py, m)?)?; + m.add_function(wrap_pyfunction!(pyfunction_with_module_and_arg, m)?)?; + m.add_function(wrap_pyfunction!( pyfunction_with_module_and_default_arg, m )?)?; - m.add_function(pyo3::wrap_pyfunction!( + m.add_function(wrap_pyfunction!( pyfunction_with_module_and_args_kwargs, m )?) diff --git a/tests/test_text_signature.rs b/tests/test_text_signature.rs index a5d40ca95ab..13fa656450c 100644 --- a/tests/test_text_signature.rs +++ b/tests/test_text_signature.rs @@ -119,7 +119,7 @@ fn test_function() { fn test_pyfn() { #[pymodule] fn my_module(_py: Python, m: &PyModule) -> PyResult<()> { - #[pyfn(m, "my_function", a, b = "None", "*", c = 42)] + #[pyfn(m, a, b = "None", "*", c = 42)] #[text_signature = "(a, b=None, *, c=42)"] fn my_function(a: i32, b: Option, c: i32) { let _ = (a, b, c); diff --git a/tests/ui/deprecations.rs b/tests/ui/deprecations.rs index 5617838e6cf..eda305d4ae9 100644 --- a/tests/ui/deprecations.rs +++ b/tests/ui/deprecations.rs @@ -25,6 +25,14 @@ impl TestClass { #[name = "foo"] fn deprecated_name_pyfunction() { } +#[pymodule] +fn my_module(_py: Python, m: &PyModule) -> PyResult<()> { + #[pyfn(m, "some_name")] + fn deprecated_name_pyfn() { } + + Ok(()) +} + fn main() { } diff --git a/tests/ui/deprecations.stderr b/tests/ui/deprecations.stderr index 18e50823de1..5ace1089d84 100644 --- a/tests/ui/deprecations.stderr +++ b/tests/ui/deprecations.stderr @@ -27,3 +27,9 @@ error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: u | 25 | #[name = "foo"] | ^ + +error: use of deprecated constant `pyo3::impl_::deprecations::PYFN_NAME_ARGUMENT`: use `#[pyfn(m)] #[pyo3(name = "...")]` instead of `#[pyfn(m, "...")]` + --> $DIR/deprecations.rs:30:15 + | +30 | #[pyfn(m, "some_name")] + | ^^^^^^^^^^^ diff --git a/tests/ui/invalid_need_module_arg_position.rs b/tests/ui/invalid_need_module_arg_position.rs index 607b21273f6..b8837031a93 100644 --- a/tests/ui/invalid_need_module_arg_position.rs +++ b/tests/ui/invalid_need_module_arg_position.rs @@ -2,11 +2,11 @@ use pyo3::prelude::*; #[pymodule] fn module(_py: Python, m: &PyModule) -> PyResult<()> { - #[pyfn(m, "with_module", pass_module)] + #[pyfn(m, pass_module)] fn fail(string: &str, module: &PyModule) -> PyResult<&str> { module.name() } Ok(()) } -fn main(){} \ No newline at end of file +fn main(){}