Skip to content

Commit a927c7e

Browse files
committed
generate individual deprecation messages per function
1 parent 56ba1ea commit a927c7e

File tree

9 files changed

+131
-76
lines changed

9 files changed

+131
-76
lines changed

guide/src/function/signature.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ num=-1
123123
124124
<div class="warning">
125125
126-
⚠️ Warning: This behaviour is phased out 🛠️
126+
⚠️ Warning: This behaviour is being phased out 🛠️
127127
128128
The special casing of trailing optional arguments is deprecated. In a future `pyo3` version, arguments of type `Option<..>` will share the same behaviour as other arguments, they are required unless a default is set using `#[pyo3(signature = (...))]`.
129129

pyo3-macros-backend/src/deprecations.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::utils::Ctx;
1+
use crate::{
2+
method::{FnArg, FnSpec},
3+
utils::Ctx,
4+
};
25
use proc_macro2::{Span, TokenStream};
36
use quote::{quote_spanned, ToTokens};
47

@@ -45,3 +48,50 @@ impl<'ctx> ToTokens for Deprecations<'ctx> {
4548
}
4649
}
4750
}
51+
52+
pub(crate) fn deprecate_trailing_option_default(spec: &FnSpec<'_>) -> TokenStream {
53+
if spec.signature.attribute.is_none()
54+
&& spec.signature.arguments.iter().any(|arg| {
55+
if let FnArg::Regular(arg) = arg {
56+
arg.option_wrapped_type.is_some()
57+
} else {
58+
false
59+
}
60+
})
61+
{
62+
use std::fmt::Write;
63+
let mut deprecation_msg = String::from(
64+
"This function has implicit defaults for the trailing `Option<T>` arguments. \
65+
These implicit defaults are being phased out. Add `#[pyo3(signature = (",
66+
);
67+
spec.signature.arguments.iter().for_each(|arg| {
68+
match arg {
69+
FnArg::Regular(arg) => {
70+
if arg.option_wrapped_type.is_some() {
71+
write!(deprecation_msg, "{}=None, ", arg.name)
72+
} else {
73+
write!(deprecation_msg, "{}, ", arg.name)
74+
}
75+
}
76+
FnArg::VarArgs(arg) => write!(deprecation_msg, "{}, ", arg.name),
77+
FnArg::KwArgs(arg) => write!(deprecation_msg, "{}, ", arg.name),
78+
FnArg::Py(_) | FnArg::CancelHandle(_) => Ok(()),
79+
}
80+
.expect("writing to `String` should not fail");
81+
});
82+
83+
//remove trailing space and comma
84+
deprecation_msg.pop();
85+
deprecation_msg.pop();
86+
87+
deprecation_msg
88+
.push_str(")]` to this function to silence this warning and keep the current behavior");
89+
quote_spanned! { spec.name.span() =>
90+
#[deprecated(note = #deprecation_msg)]
91+
const SIGNATURE: () = ();
92+
const _: () = SIGNATURE;
93+
}
94+
} else {
95+
TokenStream::new()
96+
}
97+
}

pyo3-macros-backend/src/method.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use proc_macro2::{Span, TokenStream};
55
use quote::{format_ident, quote, quote_spanned, ToTokens};
66
use syn::{ext::IdentExt, spanned::Spanned, Ident, Result};
77

8+
use crate::deprecations::deprecate_trailing_option_default;
89
use crate::utils::Ctx;
910
use crate::{
1011
attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue},
@@ -708,6 +709,8 @@ impl<'a> FnSpec<'a> {
708709
quote!(#func_name)
709710
};
710711

712+
let deprecation = deprecate_trailing_option_default(self);
713+
711714
Ok(match self.convention {
712715
CallingConvention::Noargs => {
713716
let mut holders = Holders::new();
@@ -730,6 +733,7 @@ impl<'a> FnSpec<'a> {
730733
py: #pyo3_path::Python<'py>,
731734
_slf: *mut #pyo3_path::ffi::PyObject,
732735
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
736+
#deprecation
733737
let _slf_ref = &_slf;
734738
let function = #rust_name; // Shadow the function name to avoid #3017
735739
#init_holders
@@ -754,6 +758,7 @@ impl<'a> FnSpec<'a> {
754758
_nargs: #pyo3_path::ffi::Py_ssize_t,
755759
_kwnames: *mut #pyo3_path::ffi::PyObject
756760
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
761+
#deprecation
757762
let _slf_ref = &_slf;
758763
let function = #rust_name; // Shadow the function name to avoid #3017
759764
#arg_convert
@@ -778,6 +783,7 @@ impl<'a> FnSpec<'a> {
778783
_args: *mut #pyo3_path::ffi::PyObject,
779784
_kwargs: *mut #pyo3_path::ffi::PyObject
780785
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
786+
#deprecation
781787
let _slf_ref = &_slf;
782788
let function = #rust_name; // Shadow the function name to avoid #3017
783789
#arg_convert
@@ -805,6 +811,7 @@ impl<'a> FnSpec<'a> {
805811
_kwargs: *mut #pyo3_path::ffi::PyObject
806812
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
807813
use #pyo3_path::callback::IntoPyCallbackOutput;
814+
#deprecation
808815
let _slf_ref = &_slf;
809816
let function = #rust_name; // Shadow the function name to avoid #3017
810817
#arg_convert

pyo3-macros-backend/src/params.rs

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,7 @@ pub fn impl_arg_params(
134134
.arguments
135135
.iter()
136136
.enumerate()
137-
.map(|(i, arg)| {
138-
impl_arg_param(
139-
arg,
140-
spec.signature.attribute.is_some(),
141-
i,
142-
&mut 0,
143-
holders,
144-
ctx,
145-
)
146-
})
137+
.map(|(i, arg)| impl_arg_param(arg, i, &mut 0, holders, ctx))
147138
.collect();
148139
return (
149140
quote! {
@@ -183,16 +174,7 @@ pub fn impl_arg_params(
183174
.arguments
184175
.iter()
185176
.enumerate()
186-
.map(|(i, arg)| {
187-
impl_arg_param(
188-
arg,
189-
spec.signature.attribute.is_some(),
190-
i,
191-
&mut option_pos,
192-
holders,
193-
ctx,
194-
)
195-
})
177+
.map(|(i, arg)| impl_arg_param(arg, i, &mut option_pos, holders, ctx))
196178
.collect();
197179

198180
let args_handler = if spec.signature.python_signature.varargs.is_some() {
@@ -255,7 +237,6 @@ pub fn impl_arg_params(
255237

256238
fn impl_arg_param(
257239
arg: &FnArg<'_>,
258-
has_signature_attr: bool,
259240
pos: usize,
260241
option_pos: &mut usize,
261242
holders: &mut Holders,
@@ -269,14 +250,7 @@ fn impl_arg_param(
269250
let from_py_with = format_ident!("from_py_with_{}", pos);
270251
let arg_value = quote!(#args_array[#option_pos].as_deref());
271252
*option_pos += 1;
272-
let tokens = impl_regular_arg_param(
273-
arg,
274-
has_signature_attr,
275-
from_py_with,
276-
arg_value,
277-
holders,
278-
ctx,
279-
);
253+
let tokens = impl_regular_arg_param(arg, from_py_with, arg_value, holders, ctx);
280254
check_arg_for_gil_refs(tokens, holders.push_gil_refs_checker(arg.ty.span()), ctx)
281255
}
282256
FnArg::VarArgs(arg) => {
@@ -311,7 +285,6 @@ fn impl_arg_param(
311285
/// index and the index in option diverge when using py: Python
312286
pub(crate) fn impl_regular_arg_param(
313287
arg: &RegularArg<'_>,
314-
has_signature_attr: bool,
315288
from_py_with: syn::Ident,
316289
arg_value: TokenStream, // expected type: Option<&'a Bound<'py, PyAny>>
317290
holders: &mut Holders,
@@ -362,11 +335,6 @@ pub(crate) fn impl_regular_arg_param(
362335
}
363336
} else if arg.option_wrapped_type.is_some() {
364337
let holder = holders.push_holder(arg.name.span());
365-
let arg_value = if !has_signature_attr {
366-
quote_arg_span! { #pyo3_path::impl_::deprecations::deprecate_implicit_option(#arg_value) }
367-
} else {
368-
quote!(#arg_value)
369-
};
370338
quote_arg_span! {
371339
#pyo3_path::impl_::extract_argument::extract_optional_argument(
372340
#arg_value,

pyo3-macros-backend/src/pymethod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::borrow::Cow;
22

33
use crate::attributes::{NameAttribute, RenamingRule};
4+
use crate::deprecations::deprecate_trailing_option_default;
45
use crate::method::{CallingConvention, ExtractErrorMode, PyArg};
56
use crate::params::{check_arg_for_gil_refs, impl_regular_arg_param, Holders};
67
use crate::utils::Ctx;
@@ -630,15 +631,17 @@ pub fn impl_py_setter_def(
630631

631632
let tokens = impl_regular_arg_param(
632633
arg,
633-
spec.signature.attribute.is_some(),
634634
ident,
635635
quote!(::std::option::Option::Some(_value.into())),
636636
&mut holders,
637637
ctx,
638638
);
639639
let extract =
640640
check_arg_for_gil_refs(tokens, holders.push_gil_refs_checker(arg.ty.span()), ctx);
641+
642+
let deprecation = deprecate_trailing_option_default(spec);
641643
quote! {
644+
#deprecation
642645
#from_py_with
643646
let _val = #extract;
644647
}

src/impl_/deprecations.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,3 @@ impl<T> std::ops::Deref for OptionGilRefs<T> {
8585
&self.0
8686
}
8787
}
88-
89-
#[deprecated(
90-
since = "0.22.0",
91-
note = "Implicit default for trailing optional arguments is phased out. Add an explicit \
92-
`#[pyo3(signature = (...))]` attribute a to silence this warning. In a future \
93-
pyo3 version `Option<..>` arguments will be treated the same as any other argument."
94-
)]
95-
pub fn deprecate_implicit_option<T>(t: T) -> T {
96-
t
97-
}

tests/test_pyfunction.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ fn test_from_py_with_defaults() {
182182

183183
// issue 2280 combination of from_py_with and Option<T> did not compile
184184
#[pyfunction]
185+
#[pyo3(signature = (int=None))]
185186
fn from_py_with_option(#[pyo3(from_py_with = "optional_int")] int: Option<i32>) -> i32 {
186187
int.unwrap_or(0)
187188
}

tests/ui/deprecations.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ impl MyClass {
3939
#[setter]
4040
fn set_bar_bound(&self, _value: &Bound<'_, PyAny>) {}
4141

42+
#[setter]
43+
fn set_option(&self, _value: Option<i32>) {}
44+
4245
fn __eq__(&self, #[pyo3(from_py_with = "extract_gil_ref")] _other: i32) -> bool {
4346
true
4447
}
@@ -105,6 +108,10 @@ fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<i32> {
105108
obj.extract()
106109
}
107110

111+
fn extract_options(obj: &Bound<'_, PyAny>) -> PyResult<Option<i32>> {
112+
obj.extract()
113+
}
114+
108115
#[pyfunction]
109116
fn pyfunction_from_py_with(
110117
#[pyo3(from_py_with = "extract_gil_ref")] _gil_ref: i32,
@@ -126,6 +133,17 @@ fn pyfunction_option_1(_i: u32, _any: Option<i32>) {}
126133
#[pyfunction]
127134
fn pyfunction_option_2(_i: u32, _any: Option<i32>) {}
128135

136+
#[pyfunction]
137+
fn pyfunction_option_3(_i: u32, _any: Option<i32>, _foo: Option<String>) {}
138+
139+
#[pyfunction]
140+
fn pyfunction_option_4(
141+
_i: u32,
142+
#[pyo3(from_py_with = "extract_options")] _any: Option<i32>,
143+
_foo: Option<String>,
144+
) {
145+
}
146+
129147
#[derive(Debug, FromPyObject)]
130148
pub struct Zap {
131149
#[pyo3(item)]

0 commit comments

Comments
 (0)