diff --git a/guide/src/function/signature.md b/guide/src/function/signature.md index a6be70a0..bf012558 100644 --- a/guide/src/function/signature.md +++ b/guide/src/function/signature.md @@ -185,89 +185,6 @@ fn increment(x: u64, amount: Option) -> u64 { To help avoid confusion, PyO3 requires `#[pyo3(signature = (...))]` when an `Option` argument is surrounded by arguments which aren't `Option`. -## Deprecated form - -The `#[pyfunction]` macro can take the argument specification directly, but this method is deprecated in PyO3 0.18 because the `#[pyo3(signature)]` option offers a simpler syntax and better validation. - -The `#[pymethods]` macro has an `#[args]` attribute which accepts the deprecated form. - -Below are the same examples as above, but using the deprecated syntax: - -```rust -# #![allow(deprecated)] - -use pyo3::prelude::*; -use pyo3::types::PyDict; - -#[pyfunction(kwds = "**")] -fn num_kwds(kwds: Option<&PyDict>) -> usize { - kwds.map_or(0, |dict| dict.len()) -} - -#[pymodule] -fn module_with_functions(py: Python<'_>, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(num_kwds, m)?).unwrap(); - Ok(()) -} -``` - -The following parameters can be passed to the `#[pyfunction]` attribute: - - * `"/"`: positional-only arguments separator, each parameter defined before `"/"` is a - positional-only parameter. - Corresponds to python's `def meth(arg1, arg2, ..., /, argN..)`. - * `"*"`: var arguments separator, each parameter defined after `"*"` is a keyword-only parameter. - Corresponds to python's `def meth(*, arg1.., arg2=..)`. - * `args="*"`: "args" is var args, corresponds to Python's `def meth(*args)`. Type of the `args` - parameter has to be `&PyTuple`. - * `kwargs="**"`: "kwargs" receives keyword arguments, corresponds to Python's `def meth(**kwargs)`. - The type of the `kwargs` parameter has to be `Option<&PyDict>`. - * `arg="Value"`: arguments with default value. Corresponds to Python's `def meth(arg=Value)`. - If the `arg` argument is defined after var arguments, it is treated as a keyword-only argument. - Note that `Value` has to be valid rust code, PyO3 just inserts it into the generated - code unmodified. - -Example: -```rust -# #![allow(deprecated)] -# use pyo3::prelude::*; -use pyo3::types::{PyDict, PyTuple}; -# -# #[pyclass] -# struct MyClass { -# num: i32, -# } -#[pymethods] -impl MyClass { - #[new] - #[args(num = "-1")] - fn new(num: i32) -> Self { - MyClass { num } - } - - #[args(num = "10", py_args = "*", name = "\"Hello\"", py_kwargs = "**")] - fn method( - &mut self, - num: i32, - py_args: &PyTuple, - name: &str, - py_kwargs: Option<&PyDict>, - ) -> String { - let num_before = self.num; - self.num = num; - format!( - "num={} (was previously={}), py_args={:?}, name={}, py_kwargs={:?} ", - num, num_before, py_args, name, py_kwargs, - ) - } - - fn make_change(&mut self, num: i32) -> PyResult { - self.num = num; - Ok(format!("num={}", self.num)) - } -} -``` - ## Making the function signature available to Python The function signature is exposed to Python via the `__text_signature__` attribute. PyO3 automatically generates this for every `#[pyfunction]` and all `#[pymethods]` directly from the Rust function, taking into account any override done with the `#[pyo3(signature = (...))]` option. diff --git a/guide/src/migration.md b/guide/src/migration.md index 78f1f004..ce7f97e4 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -9,6 +9,59 @@ For a detailed list of all changes, see the [CHANGELOG](changelog.md). PyO3 0.20 has increased minimum Rust version to 1.56. This enables use of newer language features and simplifies maintenance of the project. +### Required arguments are no longer accepted after optional arguments + +[Trailing `Option` arguments](./function/signature.md#trailing-optional-arguments) have an automatic default of `None`. To avoid unwanted changes when modifying function signatures, in PyO3 0.18 it was deprecated to have a required argument after an `Option` argument without using `#[pyo3(signature = (...))]` to specify the intended defaults. In PyO3 0.20, this becomes a hard error. + +Before: + +```rust,ignore +#[pyfunction] +fn x_or_y(x: Option, y: u64) -> u64 { + x.unwrap_or(y) +} +``` + +After: + +```rust +# #![allow(dead_code)] +# use pyo3::prelude::*; + +#[pyfunction] +#[pyo3(signature = (x, y))] // both x and y have no defaults and are required +fn x_or_y(x: Option, y: u64) -> u64 { + x.unwrap_or(y) +} +``` + +### Remove deprecated function forms + +In PyO3 0.18 the `#[args]` attribute for `#[pymethods]`, and directly specifying the function signature in `#[pyfunction]`, was deprecated. This functionality has been removed in PyO3 0.20. + +Before: + +```rust,ignore +#[pyfunction] +#[pyo3(a, b = "0", "/")] +fn add(a: u64, b: u64) -> u64 { + a + b +} +``` + +After: + +```rust +# #![allow(dead_code)] +# use pyo3::prelude::*; + +#[pyfunction] +#[pyo3(signature = (a, b=0, /))] +fn add(a: u64, b: u64) -> u64 { + a + b +} +``` + ## from 0.18.* to 0.19 ### Access to `Python` inside `__traverse__` implementations are now forbidden diff --git a/newsfragments/3232.removed.md b/newsfragments/3232.removed.md new file mode 100644 index 00000000..d208adb7 --- /dev/null +++ b/newsfragments/3232.removed.md @@ -0,0 +1 @@ +Remove all functionality deprecated in PyO3 0.18, including `#[args]` attribute for `#[pymethods]`. diff --git a/pyo3-macros-backend/src/deprecations.rs b/pyo3-macros-backend/src/deprecations.rs index 4ea9c5d8..5309fe75 100644 --- a/pyo3-macros-backend/src/deprecations.rs +++ b/pyo3-macros-backend/src/deprecations.rs @@ -3,18 +3,12 @@ use quote::{quote_spanned, ToTokens}; pub enum Deprecation { PyClassTextSignature, - PyFunctionArguments, - PyMethodArgsAttribute, - RequiredArgumentAfterOption, } impl Deprecation { fn ident(&self, span: Span) -> syn::Ident { let string = match self { Deprecation::PyClassTextSignature => "PYCLASS_TEXT_SIGNATURE", - Deprecation::PyFunctionArguments => "PYFUNCTION_ARGUMENTS", - Deprecation::PyMethodArgsAttribute => "PYMETHODS_ARGS_ATTRIBUTE", - Deprecation::RequiredArgumentAfterOption => "REQUIRED_ARGUMENT_AFTER_OPTION", }; syn::Ident::new(string, span) } diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index a867a301..4718434f 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -1,7 +1,6 @@ use crate::attributes::{TextSignatureAttribute, TextSignatureAttributeValue}; -use crate::deprecations::{Deprecation, Deprecations}; use crate::params::impl_arg_params; -use crate::pyfunction::{DeprecatedArgs, FunctionSignature, PyFunctionArgPyO3Attributes}; +use crate::pyfunction::{FunctionSignature, PyFunctionArgPyO3Attributes}; use crate::pyfunction::{PyFunctionOptions, SignatureAttribute}; use crate::utils::{self, PythonDoc}; use proc_macro2::{Span, TokenStream}; @@ -236,7 +235,6 @@ pub struct FnSpec<'a> { pub python_name: syn::Ident, pub signature: FunctionSignature<'a>, pub output: syn::Type, - pub deprecations: Deprecations, pub convention: CallingConvention, pub text_signature: Option, pub unsafety: Option, @@ -281,16 +279,14 @@ impl<'a> FnSpec<'a> { let PyFunctionOptions { text_signature, name, - mut deprecations, signature, .. } = options; let MethodAttributes { ty: fn_type_attr, - deprecated_args, mut python_name, - } = parse_method_attributes(meth_attrs, name.map(|name| name.value.0), &mut deprecations)?; + } = parse_method_attributes(meth_attrs, name.map(|name| name.value.0))?; let (fn_type, skip_first_arg, fixed_convention) = Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?; @@ -314,15 +310,9 @@ impl<'a> FnSpec<'a> { }; let signature = if let Some(signature) = signature { - ensure_spanned!( - deprecated_args.is_none(), - signature.kw.span() => "cannot define both function signature and legacy arguments" - ); FunctionSignature::from_arguments_and_attribute(arguments, signature)? - } else if let Some(deprecated_args) = deprecated_args { - FunctionSignature::from_arguments_and_deprecated_args(arguments, deprecated_args)? } else { - FunctionSignature::from_arguments(arguments, &mut deprecations) + FunctionSignature::from_arguments(arguments)? }; let convention = @@ -335,7 +325,6 @@ impl<'a> FnSpec<'a> { python_name, signature, output: ty, - deprecations, text_signature, unsafety: sig.unsafety, }) @@ -423,7 +412,6 @@ impl<'a> FnSpec<'a> { ident: &proc_macro2::Ident, cls: Option<&syn::Type>, ) -> Result { - let deprecations = &self.deprecations; let self_conversion = self.tp.self_conversion(cls, ExtractErrorMode::Raise); let self_arg = self.tp.self_arg(); let py = syn::Ident::new("_py", Span::call_site()); @@ -457,7 +445,6 @@ impl<'a> FnSpec<'a> { _slf: *mut _pyo3::ffi::PyObject, ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { let function = #rust_name; // Shadow the function name to avoid #3017 - #deprecations #self_conversion #call } @@ -475,7 +462,6 @@ impl<'a> FnSpec<'a> { _kwnames: *mut _pyo3::ffi::PyObject ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { let function = #rust_name; // Shadow the function name to avoid #3017 - #deprecations #self_conversion #arg_convert #call @@ -493,7 +479,6 @@ impl<'a> FnSpec<'a> { _kwargs: *mut _pyo3::ffi::PyObject ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { let function = #rust_name; // Shadow the function name to avoid #3017 - #deprecations #self_conversion #arg_convert #call @@ -518,7 +503,6 @@ impl<'a> FnSpec<'a> { ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { use _pyo3::callback::IntoPyCallbackOutput; let function = #rust_name; // Shadow the function name to avoid #3017 - #deprecations #arg_convert let result = #call; let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(#py)?; @@ -636,17 +620,14 @@ impl<'a> FnSpec<'a> { #[derive(Debug)] struct MethodAttributes { ty: Option, - deprecated_args: Option, python_name: Option, } fn parse_method_attributes( attrs: &mut Vec, mut python_name: Option, - deprecations: &mut Deprecations, ) -> Result { let mut new_attrs = Vec::new(); - let mut deprecated_args = None; let mut ty: Option = None; macro_rules! set_compound_ty { @@ -754,13 +735,6 @@ fn parse_method_attributes( )) } }; - } else if path.is_ident("args") { - ensure_spanned!( - deprecated_args.is_none(), - nested.span() => "args may only be specified once" - ); - deprecations.push(Deprecation::PyMethodArgsAttribute, path.span()); - deprecated_args = Some(DeprecatedArgs::from_meta(&nested)?); } else { new_attrs.push(attr) } @@ -771,11 +745,7 @@ fn parse_method_attributes( *attrs = new_attrs; - Ok(MethodAttributes { - ty, - deprecated_args, - python_name, - }) + Ok(MethodAttributes { ty, python_name }) } const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments"; diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index 53188c7c..27856b19 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -3,19 +3,17 @@ use crate::{ self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute, FromPyWithAttribute, NameAttribute, TextSignatureAttribute, }, - deprecations::{Deprecation, Deprecations}, method::{self, CallingConvention, FnArg}, pymethod::check_generic, utils::{ensure_not_async_fn, get_pyo3_crate}, }; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{ext::IdentExt, spanned::Spanned, NestedMeta, Result}; +use syn::{ext::IdentExt, spanned::Spanned, Result}; use syn::{ - parse::{Parse, ParseBuffer, ParseStream}, + parse::{Parse, ParseStream}, token::Comma, }; -use syn::{punctuated::Punctuated, Path}; mod signature; @@ -67,181 +65,12 @@ impl PyFunctionArgPyO3Attributes { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Argument { - PosOnlyArgsSeparator, - VarArgsSeparator, - VarArgs(syn::Path), - KeywordArgs(syn::Path), - PosOnlyArg(syn::Path, Option), - Arg(syn::Path, Option), - Kwarg(syn::Path, Option), -} - -#[derive(Debug, Default)] -pub struct DeprecatedArgs { - pub arguments: Vec, - has_kw: bool, - has_posonly_args: bool, - has_varargs: bool, - has_kwargs: bool, -} - -// Deprecated parsing mode for the signature -impl syn::parse::Parse for DeprecatedArgs { - fn parse(input: &ParseBuffer<'_>) -> syn::Result { - let attr = Punctuated::::parse_terminated(input)?; - Self::from_meta(&attr) - } -} - -impl DeprecatedArgs { - pub fn from_meta<'a>(iter: impl IntoIterator) -> syn::Result { - let mut slf = DeprecatedArgs::default(); - - for item in iter { - slf.add_item(item)? - } - Ok(slf) - } - - pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> { - match item { - NestedMeta::Meta(syn::Meta::Path(ident)) => self.add_work(item, ident)?, - NestedMeta::Meta(syn::Meta::NameValue(nv)) => { - self.add_name_value(item, nv)?; - } - NestedMeta::Lit(lit) => { - self.add_literal(item, lit)?; - } - NestedMeta::Meta(syn::Meta::List(list)) => bail_spanned!( - list.span() => "list is not supported as argument" - ), - } - Ok(()) - } - - fn add_literal(&mut self, item: &NestedMeta, lit: &syn::Lit) -> syn::Result<()> { - match lit { - syn::Lit::Str(lits) if lits.value() == "*" => { - // "*" - self.vararg_is_ok(item)?; - self.has_varargs = true; - self.arguments.push(Argument::VarArgsSeparator); - Ok(()) - } - syn::Lit::Str(lits) if lits.value() == "/" => { - // "/" - self.posonly_arg_is_ok(item)?; - self.has_posonly_args = true; - // any arguments _before_ this become positional-only - self.arguments.iter_mut().for_each(|a| { - if let Argument::Arg(path, name) = a { - *a = Argument::PosOnlyArg(path.clone(), name.clone()); - } else { - unreachable!(); - } - }); - self.arguments.push(Argument::PosOnlyArgsSeparator); - Ok(()) - } - _ => bail_spanned!(item.span() => "expected \"/\" or \"*\""), - } - } - - fn add_work(&mut self, item: &NestedMeta, path: &Path) -> syn::Result<()> { - ensure_spanned!( - !(self.has_kw || self.has_kwargs), - item.span() => "positional argument or varargs(*) not allowed after keyword arguments" - ); - if self.has_varargs { - self.arguments.push(Argument::Kwarg(path.clone(), None)); - } else { - self.arguments.push(Argument::Arg(path.clone(), None)); - } - Ok(()) - } - - fn posonly_arg_is_ok(&self, item: &NestedMeta) -> syn::Result<()> { - ensure_spanned!( - !(self.has_posonly_args || self.has_kwargs || self.has_varargs), - item.span() => "/ is not allowed after /, varargs(*), or kwargs(**)" - ); - Ok(()) - } - - fn vararg_is_ok(&self, item: &NestedMeta) -> syn::Result<()> { - ensure_spanned!( - !(self.has_kwargs || self.has_varargs), - item.span() => "* is not allowed after varargs(*) or kwargs(**)" - ); - Ok(()) - } - - fn kw_arg_is_ok(&self, item: &NestedMeta) -> syn::Result<()> { - ensure_spanned!( - !self.has_kwargs, - item.span() => "keyword argument or kwargs(**) is not allowed after kwargs(**)" - ); - Ok(()) - } - - fn add_nv_common( - &mut self, - item: &NestedMeta, - name: &syn::Path, - value: String, - ) -> syn::Result<()> { - self.kw_arg_is_ok(item)?; - if self.has_varargs { - // kw only - self.arguments - .push(Argument::Kwarg(name.clone(), Some(value))); - } else { - self.has_kw = true; - self.arguments - .push(Argument::Arg(name.clone(), Some(value))); - } - Ok(()) - } - - fn add_name_value(&mut self, item: &NestedMeta, nv: &syn::MetaNameValue) -> syn::Result<()> { - match &nv.lit { - syn::Lit::Str(litstr) => { - if litstr.value() == "*" { - // args="*" - self.vararg_is_ok(item)?; - self.has_varargs = true; - self.arguments.push(Argument::VarArgs(nv.path.clone())); - } else if litstr.value() == "**" { - // kwargs="**" - self.kw_arg_is_ok(item)?; - self.has_kwargs = true; - self.arguments.push(Argument::KeywordArgs(nv.path.clone())); - } else { - self.add_nv_common(item, &nv.path, litstr.value())?; - } - } - syn::Lit::Int(litint) => { - self.add_nv_common(item, &nv.path, format!("{}", litint))?; - } - syn::Lit::Bool(litb) => { - self.add_nv_common(item, &nv.path, format!("{}", litb.value))?; - } - _ => bail_spanned!(nv.lit.span() => "expected a string literal"), - }; - Ok(()) - } -} - #[derive(Default)] pub struct PyFunctionOptions { pub pass_module: Option, pub name: Option, - pub deprecated_args: Option, pub signature: Option, pub text_signature: Option, - pub deprecations: Deprecations, pub krate: Option, } @@ -264,12 +93,7 @@ impl Parse for PyFunctionOptions { // TODO needs duplicate check? options.krate = Some(input.parse()?); } else { - // If not recognised attribute, this is "legacy" pyfunction syntax #[pyfunction(a, b)] - options - .deprecations - .push(Deprecation::PyFunctionArguments, input.span()); - options.deprecated_args = Some(input.parse()?); - break; + return Err(lookahead.error()); } } @@ -359,10 +183,8 @@ pub fn impl_wrap_pyfunction( let PyFunctionOptions { pass_module, name, - deprecated_args, signature, text_signature, - mut deprecations, krate, } = options; @@ -392,15 +214,9 @@ pub fn impl_wrap_pyfunction( }; let signature = if let Some(signature) = signature { - ensure_spanned!( - deprecated_args.is_none(), - signature.kw.span() => "cannot define both function signature and legacy arguments" - ); FunctionSignature::from_arguments_and_attribute(arguments, signature)? - } else if let Some(deprecated_args) = deprecated_args { - FunctionSignature::from_arguments_and_deprecated_args(arguments, deprecated_args)? } else { - FunctionSignature::from_arguments(arguments, &mut deprecations) + FunctionSignature::from_arguments(arguments)? }; let ty = method::get_return_info(&func.sig.output); @@ -412,7 +228,6 @@ pub fn impl_wrap_pyfunction( python_name, signature, output: ty, - deprecations, text_signature, unsafety: func.sig.unsafety, }; diff --git a/pyo3-macros-backend/src/pyfunction/signature.rs b/pyo3-macros-backend/src/pyfunction/signature.rs index c83dc4e4..ad705f4a 100644 --- a/pyo3-macros-backend/src/pyfunction/signature.rs +++ b/pyo3-macros-backend/src/pyfunction/signature.rs @@ -1,5 +1,3 @@ -use std::cmp::max; - use proc_macro2::{Span, TokenStream}; use quote::ToTokens; use syn::{ @@ -12,13 +10,9 @@ use syn::{ use crate::{ attributes::{kw, KeywordAttribute}, - deprecations::{Deprecation, Deprecations}, method::FnArg, - pyfunction::Argument, }; -use super::DeprecatedArgs; - pub struct Signature { paren_token: syn::token::Paren, pub items: Punctuated, @@ -430,121 +424,8 @@ impl<'a> FunctionSignature<'a> { }) } - /// The difference to `from_arguments_and_signature` is that deprecated args allowed entries to be: - /// - missing - /// - out of order - pub fn from_arguments_and_deprecated_args( - mut arguments: Vec>, - deprecated_args: DeprecatedArgs, - ) -> syn::Result { - let mut varargs = None; - let mut kwargs = None; - let mut keyword_only_parameters = Vec::new(); - - fn first_n_argument_names(arguments: &[FnArg<'_>], count: usize) -> Vec { - arguments - .iter() - .filter_map(|fn_arg| { - if fn_arg.py { - None - } else { - Some(fn_arg.name.unraw().to_string()) - } - }) - .take(count) - .collect() - } - - // Record highest counts observed based off argument positions - let mut positional_only_arguments_count = None; - let mut positional_arguments_count = None; - let mut required_positional_parameters = 0; - - let args_iter = arguments.iter_mut().filter(|arg| !arg.py); // Python<'_> arguments don't show on the Python side. - - for (i, fn_arg) in args_iter.enumerate() { - if let Some(argument) = deprecated_args - .arguments - .iter() - .find(|argument| match argument { - Argument::PosOnlyArg(path, _) - | Argument::Arg(path, _) - | Argument::Kwarg(path, _) - | Argument::VarArgs(path) - | Argument::KeywordArgs(path) => path.get_ident() == Some(fn_arg.name), - _ => false, - }) - { - match argument { - Argument::PosOnlyArg(_, default) | Argument::Arg(_, default) => { - if let Some(default) = default { - fn_arg.default = Some(syn::parse_str(default)?); - } else if fn_arg.optional.is_none() { - // Option<_> arguments always have an implicit None default with the old - // `#[args]` - required_positional_parameters = i + 1; - } - if matches!(argument, Argument::PosOnlyArg(_, _)) { - positional_only_arguments_count = Some(i + 1); - } - positional_arguments_count = Some(i + 1); - } - Argument::Kwarg(_, default) => { - fn_arg.default = default.as_deref().map(syn::parse_str).transpose()?; - keyword_only_parameters.push((fn_arg.name.to_string(), default.is_none())); - } - Argument::PosOnlyArgsSeparator => {} - Argument::VarArgsSeparator => {} - Argument::VarArgs(path) => { - fn_arg.is_varargs = true; - if let Some(ident) = path.get_ident() { - varargs = Some(ident.to_string()); - } else { - bail_spanned!(path.span() => "expected ident for *args"); - }; - } - Argument::KeywordArgs(path) => { - fn_arg.is_kwargs = true; - if let Some(ident) = path.get_ident() { - kwargs = Some(ident.to_string()); - } else { - bail_spanned!(path.span() => "expected ident for **kwargs"); - }; - } - } - } else { - // Assume this is a required positional parameter - required_positional_parameters = i + 1; - positional_arguments_count = Some(i + 1); - } - } - - // fix up state based on observations above - let positional_only_parameters = positional_only_arguments_count.unwrap_or(0); - let positional_parameters = first_n_argument_names( - &arguments, - max( - positional_arguments_count.unwrap_or(0), - positional_only_arguments_count.unwrap_or(0), - ), - ); - - Ok(FunctionSignature { - arguments, - python_signature: PythonSignature { - positional_parameters, - positional_only_parameters, - required_positional_parameters, - varargs, - keyword_only_parameters, - kwargs, - }, - attribute: None, - }) - } - /// Without `#[pyo3(signature)]` or `#[args]` - just take the Rust function arguments as positional. - pub fn from_arguments(mut arguments: Vec>, deprecations: &mut Deprecations) -> Self { + pub fn from_arguments(arguments: Vec>) -> syn::Result { let mut python_signature = PythonSignature::default(); for arg in &arguments { // Python<'_> arguments don't show in Python signature @@ -553,13 +434,12 @@ impl<'a> FunctionSignature<'a> { } if arg.optional.is_none() { - // This argument is required - if python_signature.required_positional_parameters - != python_signature.positional_parameters.len() - { - // A previous argument was not required - deprecations.push(Deprecation::RequiredArgumentAfterOption, arg.name.span()); - } + // This argument is required, all previous arguments must also have been required + ensure_spanned!( + python_signature.required_positional_parameters == python_signature.positional_parameters.len(), + arg.ty.span() => "required arguments after an `Option<_>` argument are ambiguous\n\ + = help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters" + ); python_signature.required_positional_parameters = python_signature.positional_parameters.len() + 1; @@ -570,20 +450,11 @@ impl<'a> FunctionSignature<'a> { .push(arg.name.unraw().to_string()); } - // Fixup any `Option<_>` arguments that were made implicitly made required by the deprecated - // branch above - for arg in arguments - .iter_mut() - .take(python_signature.required_positional_parameters) - { - arg.optional = None; - } - - Self { + Ok(Self { arguments, python_signature, attribute: None, - } + }) } fn default_value_for_parameter(&self, parameter: &str) -> String { diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 990e5240..9585c92a 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -2,8 +2,8 @@ use std::borrow::Cow; use crate::attributes::NameAttribute; use crate::method::{CallingConvention, ExtractErrorMode}; +use crate::utils; use crate::utils::{ensure_not_async_fn, PythonDoc}; -use crate::{deprecations::Deprecations, utils}; use crate::{ method::{FnArg, FnSpec, FnType, SelfType}, pyfunction::PyFunctionOptions, @@ -441,13 +441,11 @@ fn impl_py_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> syn::Result) -> _pyo3::PyResult<_pyo3::PyObject> { let function = #cls::#name; // Shadow the method name to avoid #3017 - #deprecations _pyo3::impl_::pymethods::OkWrap::wrap(#fncall, py) .map_err(::core::convert::Into::into) } @@ -496,7 +494,6 @@ pub fn impl_py_setter_def( property_type: PropertyType<'_>, ) -> Result { let python_name = property_type.null_terminated_python_name()?; - let deprecations = property_type.deprecations(); let doc = property_type.doc(); let setter_impl = match property_type { PropertyType::Descriptor { @@ -570,14 +567,13 @@ pub fn impl_py_setter_def( let method_def = quote! { #cfg_attrs - _pyo3::class::PyMethodDefType::Setter({ - #deprecations + _pyo3::class::PyMethodDefType::Setter( _pyo3::class::PySetterDef::new( #python_name, _pyo3::impl_::pymethods::PySetter(#cls::#wrapper_ident), #doc ) - }) + ) }; Ok(MethodAndMethodDef { @@ -609,7 +605,6 @@ pub fn impl_py_getter_def( property_type: PropertyType<'_>, ) -> Result { let python_name = property_type.null_terminated_python_name()?; - let deprecations = property_type.deprecations(); let doc = property_type.doc(); let getter_impl = match property_type { PropertyType::Descriptor { @@ -691,14 +686,13 @@ pub fn impl_py_getter_def( let method_def = quote! { #cfg_attrs - _pyo3::class::PyMethodDefType::Getter({ - #deprecations + _pyo3::class::PyMethodDefType::Getter( _pyo3::class::PyGetterDef::new( #python_name, _pyo3::impl_::pymethods::PyGetter(#cls::#wrapper_ident), #doc ) - }) + ) }; Ok(MethodAndMethodDef { @@ -747,13 +741,6 @@ impl PropertyType<'_> { } } - fn deprecations(&self) -> Option<&Deprecations> { - match self { - PropertyType::Descriptor { .. } => None, - PropertyType::Function { spec, .. } => Some(&spec.deprecations), - } - } - fn doc(&self) -> Cow<'_, PythonDoc> { match self { PropertyType::Descriptor { field, .. } => { diff --git a/pytests/src/deprecated_pyfunctions.rs b/pytests/src/deprecated_pyfunctions.rs deleted file mode 100644 index 260b8eb2..00000000 --- a/pytests/src/deprecated_pyfunctions.rs +++ /dev/null @@ -1,68 +0,0 @@ -#![allow(deprecated)] - -use pyo3::prelude::*; -use pyo3::types::{PyDict, PyTuple}; - -#[pyfunction] -fn none() {} - -#[pyfunction(b = "\"bar\"", "*", c = "None")] -fn simple<'a>(a: i32, b: &'a str, c: Option<&'a PyDict>) -> (i32, &'a str, Option<&'a PyDict>) { - (a, b, c) -} - -#[pyfunction(b = "\"bar\"", args = "*", c = "None")] -fn simple_args<'a>( - a: i32, - b: &'a str, - c: Option<&'a PyDict>, - args: &'a PyTuple, -) -> (i32, &'a str, &'a PyTuple, Option<&'a PyDict>) { - (a, b, args, c) -} - -#[pyfunction(b = "\"bar\"", c = "None", kwargs = "**")] -fn simple_kwargs<'a>( - a: i32, - b: &'a str, - c: Option<&'a PyDict>, - kwargs: Option<&'a PyDict>, -) -> (i32, &'a str, Option<&'a PyDict>, Option<&'a PyDict>) { - (a, b, c, kwargs) -} - -#[pyfunction(a, b = "\"bar\"", args = "*", c = "None", kwargs = "**")] -fn simple_args_kwargs<'a>( - a: i32, - b: &'a str, - args: &'a PyTuple, - c: Option<&'a PyDict>, - kwargs: Option<&'a PyDict>, -) -> ( - i32, - &'a str, - &'a PyTuple, - Option<&'a PyDict>, - Option<&'a PyDict>, -) { - (a, b, args, c, kwargs) -} - -#[pyfunction(args = "*", kwargs = "**")] -fn args_kwargs<'a>( - args: &'a PyTuple, - kwargs: Option<&'a PyDict>, -) -> (&'a PyTuple, Option<&'a PyDict>) { - (args, kwargs) -} - -#[pymodule] -pub fn deprecated_pyfunctions(_py: Python<'_>, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(none, m)?)?; - m.add_function(wrap_pyfunction!(simple, m)?)?; - m.add_function(wrap_pyfunction!(simple_args, m)?)?; - m.add_function(wrap_pyfunction!(simple_kwargs, m)?)?; - m.add_function(wrap_pyfunction!(simple_args_kwargs, m)?)?; - m.add_function(wrap_pyfunction!(args_kwargs, m)?)?; - Ok(()) -} diff --git a/pytests/src/lib.rs b/pytests/src/lib.rs index fd96f725..8724bcaa 100644 --- a/pytests/src/lib.rs +++ b/pytests/src/lib.rs @@ -5,7 +5,6 @@ use pyo3::wrap_pymodule; pub mod buf_and_str; pub mod comparisons; pub mod datetime; -pub mod deprecated_pyfunctions; pub mod dict_iter; pub mod misc; pub mod objstore; @@ -23,9 +22,6 @@ fn pyo3_pytests(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(comparisons::comparisons))?; #[cfg(not(Py_LIMITED_API))] m.add_wrapped(wrap_pymodule!(datetime::datetime))?; - m.add_wrapped(wrap_pymodule!( - deprecated_pyfunctions::deprecated_pyfunctions - ))?; m.add_wrapped(wrap_pymodule!(dict_iter::dict_iter))?; m.add_wrapped(wrap_pymodule!(misc::misc))?; m.add_wrapped(wrap_pymodule!(objstore::objstore))?; @@ -44,10 +40,6 @@ fn pyo3_pytests(py: Python<'_>, m: &PyModule) -> PyResult<()> { sys_modules.set_item("pyo3_pytests.buf_and_str", m.getattr("buf_and_str")?)?; sys_modules.set_item("pyo3_pytests.comparisons", m.getattr("comparisons")?)?; sys_modules.set_item("pyo3_pytests.datetime", m.getattr("datetime")?)?; - sys_modules.set_item( - "pyo3_pytests.deprecated_pyfunctions", - m.getattr("deprecated_pyfunctions")?, - )?; sys_modules.set_item("pyo3_pytests.dict_iter", m.getattr("dict_iter")?)?; sys_modules.set_item("pyo3_pytests.misc", m.getattr("misc")?)?; sys_modules.set_item("pyo3_pytests.objstore", m.getattr("objstore")?)?; diff --git a/pytests/tests/test_deprecated_pyfunctions.py b/pytests/tests/test_deprecated_pyfunctions.py deleted file mode 100644 index 9fb0a16c..00000000 --- a/pytests/tests/test_deprecated_pyfunctions.py +++ /dev/null @@ -1,61 +0,0 @@ -from pyo3_pytests import deprecated_pyfunctions as pyfunctions - - -def none_py(): - return None - - -def test_none_rs(): - rust = pyfunctions.none() - py = none_py() - assert rust == py - - -def simple_py(a, b="bar", *, c=None): - return a, b, c - - -def test_simple_rs(): - rust = pyfunctions.simple(1, "foo", c={1: 2}) - py = simple_py(1, "foo", c={1: 2}) - assert rust == py - - -def simple_args_py(a, b="bar", *args, c=None): - return a, b, args, c - - -def test_simple_args_rs(): - rust = pyfunctions.simple_args(1, "foo", 4, 5, 6, c={1: 2}) - py = simple_args_py(1, "foo", 4, 5, 6, c={1: 2}) - assert rust == py - - -def simple_kwargs_py(a, b="bar", c=None, **kwargs): - return a, b, c, kwargs - - -def test_simple_kwargs_rs(): - rust = pyfunctions.simple_kwargs(1, "foo", c={1: 2}, bar=4, foo=10) - py = simple_kwargs_py(1, "foo", c={1: 2}, bar=4, foo=10) - assert rust == py - - -def simple_args_kwargs_py(a, b="bar", *args, c=None, **kwargs): - return (a, b, args, c, kwargs) - - -def test_simple_args_kwargs_rs(): - rust = pyfunctions.simple_args_kwargs(1, "foo", "baz", bar=4, foo=10) - py = simple_args_kwargs_py(1, "foo", "baz", bar=4, foo=10) - assert rust == py - - -def args_kwargs_py(*args, **kwargs): - return (args, kwargs) - - -def test_args_kwargs_rs(): - rust = pyfunctions.args_kwargs(1, "foo", {1: 2}, bar=4, foo=10) - py = args_kwargs_py(1, "foo", {1: 2}, bar=4, foo=10) - assert rust == py diff --git a/src/impl_/deprecations.rs b/src/impl_/deprecations.rs index e6b8268f..4c3aabbb 100644 --- a/src/impl_/deprecations.rs +++ b/src/impl_/deprecations.rs @@ -1,23 +1,5 @@ //! Symbols used to denote deprecated usages of PyO3's proc macros. -#[deprecated( - since = "0.18.0", - note = "passing arbitrary arguments to `#[pyfunction()]` to specify the signature is being replaced by `#[pyo3(signature)]`" -)] -pub const PYFUNCTION_ARGUMENTS: () = (); - -#[deprecated( - since = "0.18.0", - note = "the `#[args]` attribute for `#[methods]` is being replaced by `#[pyo3(signature)]`" -)] -pub const PYMETHODS_ARGS_ATTRIBUTE: () = (); - -#[deprecated( - since = "0.18.0", - note = "required arguments after an `Option<_>` argument are ambiguous and being phased out\n= help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters" -)] -pub const REQUIRED_ARGUMENT_AFTER_OPTION: () = (); - #[deprecated( since = "0.19.0", note = "put `text_signature` on `#[new]` instead of `#[pyclass]`" diff --git a/src/instance.rs b/src/instance.rs index 0cbf8dbf..fe9d9682 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1106,15 +1106,6 @@ impl PyObject { { >::try_from_unchecked(self.as_ref(py)) } - - /// Casts the PyObject to a concrete Python object type. - #[deprecated(since = "0.18.0", note = "use downcast() instead")] - pub fn cast_as<'p, D>(&'p self, py: Python<'p>) -> Result<&'p D, PyDowncastError<'_>> - where - D: PyTryFrom<'p>, - { - self.downcast(py) - } } #[cfg(test)] diff --git a/src/types/any.rs b/src/types/any.rs index a545d9b2..35d1f98b 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -799,15 +799,6 @@ impl PyAny { unsafe { ffi::Py_TYPE(self.as_ptr()) } } - /// Converts this `PyAny` to a concrete Python type. - #[deprecated(since = "0.18.0", note = "use the equivalent .downcast()")] - pub fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError<'_>> - where - D: PyTryFrom<'a>, - { - self.downcast() - } - /// Downcast this `PyAny` to a concrete Python type or pyclass. /// /// Note that you can often avoid downcasting yourself by just specifying diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 4625ea16..b32c463a 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -5,7 +5,6 @@ fn test_compile_errors() { let t = trybuild::TestCases::new(); - t.compile_fail("tests/ui/invalid_macro_args.rs"); t.compile_fail("tests/ui/invalid_need_module_arg_position.rs"); t.compile_fail("tests/ui/invalid_property_args.rs"); t.compile_fail("tests/ui/invalid_proto_pymethods.rs"); diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 3f9be0a4..2a9f656a 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -162,464 +162,6 @@ fn static_method_with_args() { }); } -#[allow(deprecated)] -mod deprecated { - use super::*; - - #[pyclass] - struct MethArgs {} - - #[pymethods] - impl MethArgs { - #[args(test)] - fn get_optional(&self, test: Option) -> i32 { - test.unwrap_or(10) - } - fn get_optional2(&self, test: Option) -> Option { - test - } - #[args(test = "None")] - fn get_optional3(&self, test: Option) -> Option { - test - } - fn get_optional_positional( - &self, - _t1: Option, - t2: Option, - _t3: Option, - ) -> Option { - t2 - } - - #[args(test = "10")] - fn get_default(&self, test: i32) -> i32 { - test - } - #[args("*", test = 10)] - fn get_kwarg(&self, test: i32) -> i32 { - test - } - #[args(args = "*", kwargs = "**")] - fn get_kwargs(&self, py: Python<'_>, args: &PyTuple, kwargs: Option<&PyDict>) -> PyObject { - [args.into(), kwargs.to_object(py)].to_object(py) - } - - #[args(args = "*", kwargs = "**")] - fn get_pos_arg_kw( - &self, - py: Python<'_>, - a: i32, - args: &PyTuple, - kwargs: Option<&PyDict>, - ) -> PyObject { - [a.to_object(py), args.into(), kwargs.to_object(py)].to_object(py) - } - - #[args(a, b, "/")] - fn get_pos_only(&self, a: i32, b: i32) -> i32 { - a + b - } - - #[args(a, "/", b)] - fn get_pos_only_and_pos(&self, a: i32, b: i32) -> i32 { - a + b - } - - #[args(a, "/", b, c = 5)] - fn get_pos_only_and_pos_and_kw(&self, a: i32, b: i32, c: i32) -> i32 { - a + b + c - } - - #[args(a, "/", "*", b)] - fn get_pos_only_and_kw_only(&self, a: i32, b: i32) -> i32 { - a + b - } - - #[args(a, "/", "*", b = 3)] - fn get_pos_only_and_kw_only_with_default(&self, a: i32, b: i32) -> i32 { - a + b - } - - #[args(a, "/", b, "*", c, d = 5)] - fn get_all_arg_types_together(&self, a: i32, b: i32, c: i32, d: i32) -> i32 { - a + b + c + d - } - - #[args(a, "/", args = "*")] - fn get_pos_only_with_varargs(&self, a: i32, args: Vec) -> i32 { - a + args.iter().sum::() - } - - #[args(a, "/", kwargs = "**")] - fn get_pos_only_with_kwargs( - &self, - py: Python<'_>, - a: i32, - kwargs: Option<&PyDict>, - ) -> PyObject { - [a.to_object(py), kwargs.to_object(py)].to_object(py) - } - - #[args("*", a = 2, b = 3)] - fn get_kwargs_only_with_defaults(&self, a: i32, b: i32) -> i32 { - a + b - } - - #[args("*", a, b)] - fn get_kwargs_only(&self, a: i32, b: i32) -> i32 { - a + b - } - - #[args("*", a = 1, b)] - fn get_kwargs_only_with_some_default(&self, a: i32, b: i32) -> i32 { - a + b - } - - #[args(args = "*", a)] - fn get_args_and_required_keyword( - &self, - py: Python<'_>, - args: &PyTuple, - a: i32, - ) -> PyObject { - (args, a).to_object(py) - } - - #[args(a, b = 2, "*", c = 3)] - fn get_pos_arg_kw_sep1(&self, a: i32, b: i32, c: i32) -> i32 { - a + b + c - } - - #[args(a, "*", b = 2, c = 3)] - fn get_pos_arg_kw_sep2(&self, a: i32, b: i32, c: i32) -> i32 { - a + b + c - } - - #[args(kwargs = "**")] - fn get_pos_kw(&self, py: Python<'_>, a: i32, kwargs: Option<&PyDict>) -> PyObject { - [a.to_object(py), kwargs.to_object(py)].to_object(py) - } - // "args" can be anything that can be extracted from PyTuple - #[args(args = "*")] - fn args_as_vec(&self, args: Vec) -> i32 { - args.iter().sum() - } - } - - #[test] - fn meth_args() { - Python::with_gil(|py| { - let inst = Py::new(py, MethArgs {}).unwrap(); - - py_run!(py, inst, "assert inst.get_optional() == 10"); - py_run!(py, inst, "assert inst.get_optional(100) == 100"); - py_run!(py, inst, "assert inst.get_optional2() == None"); - py_run!(py, inst, "assert inst.get_optional2(100) == 100"); - py_run!(py, inst, "assert inst.get_optional3() == None"); - py_run!(py, inst, "assert inst.get_optional3(100) == 100"); - py_run!( - py, - inst, - "assert inst.get_optional_positional(1, 2, 3) == 2" - ); - py_run!(py, inst, "assert inst.get_optional_positional(1) == None"); - py_run!(py, inst, "assert inst.get_default() == 10"); - py_run!(py, inst, "assert inst.get_default(100) == 100"); - py_run!(py, inst, "assert inst.get_kwarg() == 10"); - py_expect_exception!(py, inst, "inst.get_kwarg(100)", PyTypeError); - py_run!(py, inst, "assert inst.get_kwarg(test=100) == 100"); - py_run!(py, inst, "assert inst.get_kwargs() == [(), None]"); - py_run!(py, inst, "assert inst.get_kwargs(1,2,3) == [(1,2,3), None]"); - py_run!( - py, - inst, - "assert inst.get_kwargs(t=1,n=2) == [(), {'t': 1, 'n': 2}]" - ); - py_run!( - py, - inst, - "assert inst.get_kwargs(1,2,3,t=1,n=2) == [(1,2,3), {'t': 1, 'n': 2}]" - ); - - py_run!(py, inst, "assert inst.get_pos_arg_kw(1) == [1, (), None]"); - py_run!( - py, - inst, - "assert inst.get_pos_arg_kw(1, 2, 3) == [1, (2, 3), None]" - ); - py_run!( - py, - inst, - "assert inst.get_pos_arg_kw(1, b=2) == [1, (), {'b': 2}]" - ); - py_run!(py, inst, "assert inst.get_pos_arg_kw(a=1) == [1, (), None]"); - py_expect_exception!(py, inst, "inst.get_pos_arg_kw()", PyTypeError); - py_expect_exception!(py, inst, "inst.get_pos_arg_kw(1, a=1)", PyTypeError); - py_expect_exception!(py, inst, "inst.get_pos_arg_kw(b=2)", PyTypeError); - - py_run!(py, inst, "assert inst.get_pos_only(10, 11) == 21"); - py_expect_exception!(py, inst, "inst.get_pos_only(10, b = 11)", PyTypeError); - py_expect_exception!(py, inst, "inst.get_pos_only(a = 10, b = 11)", PyTypeError); - - py_run!(py, inst, "assert inst.get_pos_only_and_pos(10, 11) == 21"); - py_run!( - py, - inst, - "assert inst.get_pos_only_and_pos(10, b = 11) == 21" - ); - py_expect_exception!( - py, - inst, - "inst.get_pos_only_and_pos(a = 10, b = 11)", - PyTypeError - ); - - py_run!( - py, - inst, - "assert inst.get_pos_only_and_pos_and_kw(10, 11) == 26" - ); - py_run!( - py, - inst, - "assert inst.get_pos_only_and_pos_and_kw(10, b = 11) == 26" - ); - py_run!( - py, - inst, - "assert inst.get_pos_only_and_pos_and_kw(10, 11, c = 0) == 21" - ); - py_run!( - py, - inst, - "assert inst.get_pos_only_and_pos_and_kw(10, b = 11, c = 0) == 21" - ); - py_expect_exception!( - py, - inst, - "inst.get_pos_only_and_pos_and_kw(a = 10, b = 11)", - PyTypeError - ); - - py_run!( - py, - inst, - "assert inst.get_pos_only_and_kw_only(10, b = 11) == 21" - ); - py_expect_exception!( - py, - inst, - "inst.get_pos_only_and_kw_only(10, 11)", - PyTypeError - ); - py_expect_exception!( - py, - inst, - "inst.get_pos_only_and_kw_only(a = 10, b = 11)", - PyTypeError - ); - - py_run!( - py, - inst, - "assert inst.get_pos_only_and_kw_only_with_default(10) == 13" - ); - py_run!( - py, - inst, - "assert inst.get_pos_only_and_kw_only_with_default(10, b = 11) == 21" - ); - py_expect_exception!( - py, - inst, - "inst.get_pos_only_and_kw_only_with_default(10, 11)", - PyTypeError - ); - py_expect_exception!( - py, - inst, - "inst.get_pos_only_and_kw_only_with_default(a = 10, b = 11)", - PyTypeError - ); - - py_run!( - py, - inst, - "assert inst.get_all_arg_types_together(10, 10, c = 10) == 35" - ); - py_run!( - py, - inst, - "assert inst.get_all_arg_types_together(10, 10, c = 10, d = 10) == 40" - ); - py_run!( - py, - inst, - "assert inst.get_all_arg_types_together(10, b = 10, c = 10, d = 10) == 40" - ); - py_expect_exception!( - py, - inst, - "inst.get_all_arg_types_together(10, 10, 10)", - PyTypeError - ); - py_expect_exception!( - py, - inst, - "inst.get_all_arg_types_together(a = 10, b = 10, c = 10)", - PyTypeError - ); - - py_run!(py, inst, "assert inst.get_pos_only_with_varargs(10) == 10"); - py_run!( - py, - inst, - "assert inst.get_pos_only_with_varargs(10, 10) == 20" - ); - py_run!( - py, - inst, - "assert inst.get_pos_only_with_varargs(10, 10, 10, 10, 10) == 50" - ); - py_expect_exception!( - py, - inst, - "inst.get_pos_only_with_varargs(a = 10)", - PyTypeError - ); - - py_run!( - py, - inst, - "assert inst.get_pos_only_with_kwargs(10) == [10, None]" - ); - py_run!( - py, - inst, - "assert inst.get_pos_only_with_kwargs(10, b = 10) == [10, {'b': 10}]" - ); - py_run!( - py, - inst, - "assert inst.get_pos_only_with_kwargs(10, b = 10, c = 10, d = 10, e = 10) == [10, {'b': 10, 'c': 10, 'd': 10, 'e': 10}]" - ); - py_expect_exception!( - py, - inst, - "inst.get_pos_only_with_kwargs(a = 10)", - PyTypeError - ); - py_expect_exception!( - py, - inst, - "inst.get_pos_only_with_kwargs(a = 10, b = 10)", - PyTypeError - ); - - py_run!(py, inst, "assert inst.get_kwargs_only_with_defaults() == 5"); - py_run!( - py, - inst, - "assert inst.get_kwargs_only_with_defaults(a = 8) == 11" - ); - py_run!( - py, - inst, - "assert inst.get_kwargs_only_with_defaults(b = 8) == 10" - ); - py_run!( - py, - inst, - "assert inst.get_kwargs_only_with_defaults(a = 1, b = 1) == 2" - ); - py_run!( - py, - inst, - "assert inst.get_kwargs_only_with_defaults(b = 1, a = 1) == 2" - ); - - py_run!(py, inst, "assert inst.get_kwargs_only(a = 1, b = 1) == 2"); - py_run!(py, inst, "assert inst.get_kwargs_only(b = 1, a = 1) == 2"); - - py_run!( - py, - inst, - "assert inst.get_kwargs_only_with_some_default(a = 2, b = 1) == 3" - ); - py_run!( - py, - inst, - "assert inst.get_kwargs_only_with_some_default(b = 1) == 2" - ); - py_run!( - py, - inst, - "assert inst.get_kwargs_only_with_some_default(b = 1, a = 2) == 3" - ); - py_expect_exception!( - py, - inst, - "inst.get_kwargs_only_with_some_default()", - PyTypeError - ); - - py_run!( - py, - inst, - "assert inst.get_args_and_required_keyword(1, 2, a=3) == ((1, 2), 3)" - ); - py_run!( - py, - inst, - "assert inst.get_args_and_required_keyword(a=1) == ((), 1)" - ); - py_expect_exception!( - py, - inst, - "inst.get_args_and_required_keyword()", - PyTypeError - ); - - py_run!(py, inst, "assert inst.get_pos_arg_kw_sep1(1) == 6"); - py_run!(py, inst, "assert inst.get_pos_arg_kw_sep1(1, 2) == 6"); - py_run!( - py, - inst, - "assert inst.get_pos_arg_kw_sep1(1, 2, c=13) == 16" - ); - py_run!( - py, - inst, - "assert inst.get_pos_arg_kw_sep1(a=1, b=2, c=13) == 16" - ); - py_run!( - py, - inst, - "assert inst.get_pos_arg_kw_sep1(b=2, c=13, a=1) == 16" - ); - py_run!( - py, - inst, - "assert inst.get_pos_arg_kw_sep1(c=13, b=2, a=1) == 16" - ); - py_expect_exception!(py, inst, "inst.get_pos_arg_kw_sep1(1, 2, 3)", PyTypeError); - - py_run!(py, inst, "assert inst.get_pos_arg_kw_sep2(1) == 6"); - py_run!( - py, - inst, - "assert inst.get_pos_arg_kw_sep2(1, b=12, c=13) == 26" - ); - py_expect_exception!(py, inst, "inst.get_pos_arg_kw_sep2(1, 2)", PyTypeError); - - py_run!(py, inst, "assert inst.get_pos_kw(1, b=2) == [1, {'b': 2}]"); - py_expect_exception!(py, inst, "inst.get_pos_kw(1,2)", PyTypeError); - - py_run!(py, inst, "assert inst.args_as_vec(1,2,3) == 6"); - }); - } -} - #[pyclass] struct MethSignature {} diff --git a/tests/test_pyfunction.rs b/tests/test_pyfunction.rs index 8709f23a..481ae0e8 100644 --- a/tests/test_pyfunction.rs +++ b/tests/test_pyfunction.rs @@ -478,43 +478,6 @@ fn use_pyfunction() { }) } -#[test] -#[allow(deprecated)] -fn required_argument_after_option() { - #[pyfunction] - pub fn foo(x: Option, y: i32) -> i32 { - y + x.unwrap_or_default() - } - - Python::with_gil(|py| { - let f = wrap_pyfunction!(foo, py).unwrap(); - - // it is an error to call this function with no arguments - py_expect_exception!( - py, - f, - "f()", - PyTypeError, - "foo() missing 2 required positional arguments: 'x' and 'y'" - ); - - // it is an error to call this function with one argument - py_expect_exception!( - py, - f, - "f(None)", - PyTypeError, - "foo() missing 1 required positional argument: 'y'" - ); - - // ok to call with two arguments - py_assert!(py, f, "f(None, 5) == 5"); - - // ok to call with keyword arguments - py_assert!(py, f, "f(x=None, y=5) == 5"); - }) -} - #[pyclass] struct Key(String); diff --git a/tests/test_variable_arguments_deprecated.rs b/tests/test_variable_arguments_deprecated.rs deleted file mode 100644 index 1c588a94..00000000 --- a/tests/test_variable_arguments_deprecated.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![cfg(feature = "macros")] -#![allow(deprecated)] - -use pyo3::prelude::*; -use pyo3::types::{PyDict, PyTuple}; - -mod common; - -#[pyclass] -struct MyClass {} - -#[pymethods] -impl MyClass { - #[staticmethod] - #[args(args = "*")] - fn test_args(args: &PyTuple) -> &PyTuple { - args - } - - #[staticmethod] - #[args(kwargs = "**")] - fn test_kwargs(kwargs: Option<&PyDict>) -> Option<&PyDict> { - kwargs - } -} - -#[test] -fn variable_args() { - Python::with_gil(|py| { - let my_obj = py.get_type::(); - py_assert!(py, my_obj, "my_obj.test_args() == ()"); - py_assert!(py, my_obj, "my_obj.test_args(1) == (1,)"); - py_assert!(py, my_obj, "my_obj.test_args(1, 2) == (1, 2)"); - }); -} - -#[test] -fn variable_kwargs() { - Python::with_gil(|py| { - let my_obj = py.get_type::(); - py_assert!(py, my_obj, "my_obj.test_kwargs() == None"); - py_assert!(py, my_obj, "my_obj.test_kwargs(test=1) == {'test': 1}"); - py_assert!( - py, - my_obj, - "my_obj.test_kwargs(test1=1, test2=2) == {'test1':1, 'test2':2}" - ); - }); -} diff --git a/tests/ui/deprecations.rs b/tests/ui/deprecations.rs index f36ddf8c..f6477668 100644 --- a/tests/ui/deprecations.rs +++ b/tests/ui/deprecations.rs @@ -2,25 +2,8 @@ use pyo3::prelude::*; -#[pyfunction(_opt = "None", x = "5")] -fn function_with_args(_opt: Option, _x: i32) {} - -#[pyfunction] -fn function_with_required_after_option(_opt: Option, _x: i32) {} - #[pyclass] +#[pyo3(text_signature = "()")] struct MyClass; -#[pymethods] -impl MyClass { - #[args(_opt = "None", x = "5")] - fn function_with_args(&self, _opt: Option, _x: i32) {} - - #[args(_has_default = 1)] - fn default_arg_before_required_deprecated(&self, _has_default: isize, _required: isize) {} -} - -fn main() { - function_with_required_after_option(None, 0); - function_with_args(None, 0); -} +fn main() {} diff --git a/tests/ui/deprecations.stderr b/tests/ui/deprecations.stderr index e99f8b05..71c315e3 100644 --- a/tests/ui/deprecations.stderr +++ b/tests/ui/deprecations.stderr @@ -1,30 +1,11 @@ -error: use of deprecated constant `pyo3::impl_::deprecations::PYFUNCTION_ARGUMENTS`: passing arbitrary arguments to `#[pyfunction()]` to specify the signature is being replaced by `#[pyo3(signature)]` - --> tests/ui/deprecations.rs:5:14 +error: use of deprecated constant `pyo3::impl_::deprecations::PYCLASS_TEXT_SIGNATURE`: put `text_signature` on `#[new]` instead of `#[pyclass]` + --> tests/ui/deprecations.rs:6:8 | -5 | #[pyfunction(_opt = "None", x = "5")] - | ^^^^ +6 | #[pyo3(text_signature = "()")] + | ^^^^^^^^^^^^^^ | note: the lint level is defined here --> tests/ui/deprecations.rs:1:9 | 1 | #![deny(deprecated)] | ^^^^^^^^^^ - -error: use of deprecated constant `pyo3::impl_::deprecations::REQUIRED_ARGUMENT_AFTER_OPTION`: required arguments after an `Option<_>` argument are ambiguous and being phased out - = help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters - --> tests/ui/deprecations.rs:9:59 - | -9 | fn function_with_required_after_option(_opt: Option, _x: i32) {} - | ^^ - -error: use of deprecated constant `pyo3::impl_::deprecations::PYMETHODS_ARGS_ATTRIBUTE`: the `#[args]` attribute for `#[methods]` is being replaced by `#[pyo3(signature)]` - --> tests/ui/deprecations.rs:16:7 - | -16 | #[args(_opt = "None", x = "5")] - | ^^^^ - -error: use of deprecated constant `pyo3::impl_::deprecations::PYMETHODS_ARGS_ATTRIBUTE`: the `#[args]` attribute for `#[methods]` is being replaced by `#[pyo3(signature)]` - --> tests/ui/deprecations.rs:19:7 - | -19 | #[args(_has_default = 1)] - | ^^^^ diff --git a/tests/ui/invalid_macro_args.rs b/tests/ui/invalid_macro_args.rs deleted file mode 100644 index f64ed4b5..00000000 --- a/tests/ui/invalid_macro_args.rs +++ /dev/null @@ -1,33 +0,0 @@ -use pyo3::prelude::*; - -#[pyfunction(a = 5, b)] -fn pos_after_kw(py: Python<'_>, a: i32, b: i32) -> PyObject { - [a.to_object(py), vararg.into()].to_object(py) -} - -#[pyfunction(kwargs = "**", a = 5)] -fn kw_after_kwargs(py: Python<'_>, kwargs: &PyDict, a: i32) -> PyObject { - [a.to_object(py), vararg.into()].to_object(py) -} - -#[pyfunction(a, "*", b, "/", c)] -fn pos_only_after_kw_only(py: Python<'_>, a: i32, b: i32, c: i32) -> i32 { - a + b + c -} - -#[pyfunction(a, args="*", "/", b)] -fn pos_only_after_args(py: Python<'_>, a: i32, args: Vec, b: i32) -> i32 { - a + b + c -} - -#[pyfunction(a, kwargs="**", "/", b)] -fn pos_only_after_kwargs(py: Python<'_>, a: i32, args: Vec, b: i32) -> i32 { - a + b -} - -#[pyfunction(kwargs = "**", "*", a)] -fn kw_only_after_kwargs(py: Python<'_>, kwargs: &PyDict, a: i32) -> PyObject { - [a.to_object(py), vararg.into()].to_object(py) -} - -fn main() {} diff --git a/tests/ui/invalid_macro_args.stderr b/tests/ui/invalid_macro_args.stderr deleted file mode 100644 index a2bc0d94..00000000 --- a/tests/ui/invalid_macro_args.stderr +++ /dev/null @@ -1,35 +0,0 @@ -error: positional argument or varargs(*) not allowed after keyword arguments - --> tests/ui/invalid_macro_args.rs:3:21 - | -3 | #[pyfunction(a = 5, b)] - | ^ - -error: keyword argument or kwargs(**) is not allowed after kwargs(**) - --> tests/ui/invalid_macro_args.rs:8:29 - | -8 | #[pyfunction(kwargs = "**", a = 5)] - | ^ - -error: / is not allowed after /, varargs(*), or kwargs(**) - --> tests/ui/invalid_macro_args.rs:13:25 - | -13 | #[pyfunction(a, "*", b, "/", c)] - | ^^^ - -error: / is not allowed after /, varargs(*), or kwargs(**) - --> tests/ui/invalid_macro_args.rs:18:27 - | -18 | #[pyfunction(a, args="*", "/", b)] - | ^^^ - -error: / is not allowed after /, varargs(*), or kwargs(**) - --> tests/ui/invalid_macro_args.rs:23:30 - | -23 | #[pyfunction(a, kwargs="**", "/", b)] - | ^^^ - -error: * is not allowed after varargs(*) or kwargs(**) - --> tests/ui/invalid_macro_args.rs:28:29 - | -28 | #[pyfunction(kwargs = "**", "*", a)] - | ^^^ diff --git a/tests/ui/invalid_pyfunction_signatures.stderr b/tests/ui/invalid_pyfunction_signatures.stderr index 44031cce..dbca169d 100644 --- a/tests/ui/invalid_pyfunction_signatures.stderr +++ b/tests/ui/invalid_pyfunction_signatures.stderr @@ -16,11 +16,11 @@ error: expected argument from function definition `y` but got argument `x` 13 | #[pyo3(signature = (x))] | ^ -error: cannot define both function signature and legacy arguments - --> tests/ui/invalid_pyfunction_signatures.rs:19:8 +error: expected one of: `name`, `pass_module`, `signature`, `text_signature`, `crate` + --> tests/ui/invalid_pyfunction_signatures.rs:18:14 | -19 | #[pyo3(signature = (x))] - | ^^^^^^^^^ +18 | #[pyfunction(x)] + | ^ error: `*args` not allowed after `*` --> tests/ui/invalid_pyfunction_signatures.rs:25:24 @@ -52,8 +52,8 @@ error: arguments of type `Python` must not be part of the signature 47 | #[pyfunction(signature = (py))] | ^^ -error: cannot define both function signature and legacy arguments - --> tests/ui/invalid_pyfunction_signatures.rs:58:12 +error: cannot find attribute `args` in this scope + --> tests/ui/invalid_pyfunction_signatures.rs:57:7 | -58 | #[pyo3(signature = (x))] - | ^^^^^^^^^ +57 | #[args(x)] + | ^^^^ diff --git a/tests/ui/invalid_pyfunctions.rs b/tests/ui/invalid_pyfunctions.rs index 7ddad156..2a30a0d1 100644 --- a/tests/ui/invalid_pyfunctions.rs +++ b/tests/ui/invalid_pyfunctions.rs @@ -15,4 +15,7 @@ fn wildcard_argument(_: i32) {} #[pyfunction] fn destructured_argument((a, b): (i32, i32)) {} +#[pyfunction] +fn function_with_required_after_option(_opt: Option, _x: i32) {} + fn main() {} diff --git a/tests/ui/invalid_pyfunctions.stderr b/tests/ui/invalid_pyfunctions.stderr index a44f208c..9f140926 100644 --- a/tests/ui/invalid_pyfunctions.stderr +++ b/tests/ui/invalid_pyfunctions.stderr @@ -29,3 +29,10 @@ error: destructuring in arguments is not supported | 16 | fn destructured_argument((a, b): (i32, i32)) {} | ^^^^^^ + +error: required arguments after an `Option<_>` argument are ambiguous + = help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters + --> tests/ui/invalid_pyfunctions.rs:19:63 + | +19 | fn function_with_required_after_option(_opt: Option, _x: i32) {} + | ^^^