Skip to content

Commit 69a30e7

Browse files
authored
Introspection: use object as input argument type for method returning NotImplemented on conversion error (#5841)
* Introspection: use object as input argument type for method returning NotImplemented on conversion error * Add changelog
1 parent 42a35d9 commit 69a30e7

11 files changed

Lines changed: 259 additions & 53 deletions

File tree

newsfragments/5841.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Stubs: use `object` as the input annotation type of magic methods returning `NonImplemented` if the input value is not of the correct type

pyo3-macros-backend/src/introspection.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ pub fn function_introspection_code(
106106
returns: ReturnType,
107107
decorators: impl IntoIterator<Item = PyExpr>,
108108
is_async: bool,
109+
is_returning_not_implemented_on_extraction_error: bool,
109110
doc: Option<&PythonDoc>,
110111
parent: Option<&Type>,
111112
) -> TokenStream {
@@ -114,7 +115,12 @@ pub fn function_introspection_code(
114115
("name", IntrospectionNode::String(name.into())),
115116
(
116117
"arguments",
117-
arguments_introspection_data(signature, first_argument, parent),
118+
arguments_introspection_data(
119+
signature,
120+
first_argument,
121+
is_returning_not_implemented_on_extraction_error,
122+
parent,
123+
),
118124
),
119125
(
120126
"returns",
@@ -213,6 +219,7 @@ pub fn attribute_introspection_code(
213219
fn arguments_introspection_data<'a>(
214220
signature: &'a FunctionSignature<'a>,
215221
first_argument: Option<&'a str>,
222+
is_returning_not_implemented_on_extraction_error: bool,
216223
class_type: Option<&Type>,
217224
) -> IntrospectionNode<'a> {
218225
let mut argument_desc = signature.arguments.iter().filter(|arg| {
@@ -248,7 +255,12 @@ fn arguments_introspection_data<'a>(
248255
} else {
249256
panic!("Less arguments than in python signature");
250257
};
251-
let arg = argument_introspection_data(param, arg_desc, class_type);
258+
let arg = argument_introspection_data(
259+
param,
260+
arg_desc,
261+
is_returning_not_implemented_on_extraction_error,
262+
class_type,
263+
);
252264
if i < signature.python_signature.positional_only_parameters {
253265
posonlyargs.push(arg);
254266
} else {
@@ -271,7 +283,12 @@ fn arguments_introspection_data<'a>(
271283
let Some(FnArg::Regular(arg_desc)) = argument_desc.next() else {
272284
panic!("Less arguments than in python signature");
273285
};
274-
kwonlyargs.push(argument_introspection_data(param, arg_desc, class_type));
286+
kwonlyargs.push(argument_introspection_data(
287+
param,
288+
arg_desc,
289+
is_returning_not_implemented_on_extraction_error,
290+
class_type,
291+
));
275292
}
276293

277294
if let Some(param) = &signature.python_signature.kwargs {
@@ -307,14 +324,18 @@ fn arguments_introspection_data<'a>(
307324
fn argument_introspection_data<'a>(
308325
name: &'a str,
309326
desc: &'a RegularArg<'_>,
327+
is_returning_not_implemented_on_extraction_error: bool,
310328
class_type: Option<&Type>,
311329
) -> AttributedIntrospectionNode<'a> {
312330
let mut params: HashMap<_, _> = [("name", IntrospectionNode::String(name.into()))].into();
313331
if let Some(expr) = &desc.default_value {
314332
params.insert("default", PyExpr::constant_from_expression(expr).into());
315333
}
316334

317-
if let Some(annotation) = &desc.annotation {
335+
if is_returning_not_implemented_on_extraction_error {
336+
// all inputs are allowed, we use `object`
337+
params.insert("annotation", PyExpr::builtin("object").into());
338+
} else if let Some(annotation) = &desc.annotation {
318339
params.insert("annotation", annotation.clone().into());
319340
} else if desc.from_py_with.is_none() {
320341
// If from_py_with is set we don't know anything on the input type

pyo3-macros-backend/src/pyclass.rs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ fn implement_py_formatting(
968968
names: &["__str__"],
969969
arguments: Vec::new(),
970970
returns: parse_quote! { ::std::string::String },
971+
is_returning_not_implemented_on_extraction_error: false,
971972
},
972973
ctx,
973974
)
@@ -1057,6 +1058,7 @@ fn impl_simple_enum(
10571058
names: &["__repr__"],
10581059
arguments: Vec::new(),
10591060
returns: parse_quote! { &'static str },
1061+
is_returning_not_implemented_on_extraction_error: false,
10601062
},
10611063
ctx,
10621064
)?;
@@ -1090,6 +1092,7 @@ fn impl_simple_enum(
10901092
names: &["__int__"],
10911093
arguments: Vec::new(),
10921094
returns: parse_quote!(#repr_type),
1095+
is_returning_not_implemented_on_extraction_error: false,
10931096
},
10941097
ctx,
10951098
)?;
@@ -1609,6 +1612,7 @@ fn impl_complex_enum_tuple_variant_len(
16091612
names: &["__len__"],
16101613
arguments: Vec::new(),
16111614
returns: parse_quote! { ::std::primitive::usize },
1615+
is_returning_not_implemented_on_extraction_error: false,
16121616
},
16131617
ctx,
16141618
)?;
@@ -1658,6 +1662,7 @@ fn impl_complex_enum_tuple_variant_getitem(
16581662
annotation: None,
16591663
})],
16601664
returns: parse_quote! { #pyo3_path::Py<#pyo3_path::PyAny> }, // TODO: figure out correct type
1665+
is_returning_not_implemented_on_extraction_error: false,
16611666
},
16621667
ctx,
16631668
)?;
@@ -1779,6 +1784,7 @@ fn impl_complex_enum_variant_qualname(
17791784
struct FunctionIntrospectionData<'a> {
17801785
names: &'a [&'a str],
17811786
arguments: Vec<FnArg<'a>>,
1787+
is_returning_not_implemented_on_extraction_error: bool,
17821788
returns: syn::Type,
17831789
}
17841790

@@ -1799,6 +1805,7 @@ impl FunctionIntrospectionData<'_> {
17991805
parse_quote!(-> #returns),
18001806
[],
18011807
false,
1808+
self.is_returning_not_implemented_on_extraction_error,
18021809
None,
18031810
Some(cls),
18041811
)
@@ -2043,6 +2050,7 @@ fn complex_enum_struct_variant_new<'a>(
20432050
&spec,
20442051
&[],
20452052
&variant_cls_type,
2053+
false,
20462054
ctx,
20472055
));
20482056
Ok(def)
@@ -2107,6 +2115,7 @@ fn complex_enum_tuple_variant_new<'a>(
21072115
&spec,
21082116
&[],
21092117
&variant_cls_type,
2118+
false,
21102119
ctx,
21112120
));
21122121
Ok(def)
@@ -2151,6 +2160,7 @@ fn complex_enum_variant_field_getter(
21512160
&spec,
21522161
field_attrs,
21532162
variant_cls_type,
2163+
false,
21542164
ctx,
21552165
));
21562166
Ok(getter)
@@ -2201,6 +2211,7 @@ fn descriptors_to_items(
22012211
parse_quote!(-> #return_type),
22022212
vec![PyExpr::builtin("property")],
22032213
false,
2214+
false,
22042215
utils::get_doc(&field.attrs, None).as_ref(),
22052216
Some(&parse_quote!(#cls)),
22062217
));
@@ -2255,7 +2266,8 @@ fn descriptors_to_items(
22552266
"setter",
22562267
)],
22572268
false,
2258-
utils::get_doc(&field.attrs, None).as_ref(),
2269+
false,
2270+
get_doc(&field.attrs, None).as_ref(),
22592271
Some(&parse_quote!(#cls)),
22602272
));
22612273
}
@@ -2411,27 +2423,20 @@ fn pyclass_richcmp_simple_enum(
24112423
}
24122424
};
24132425
#[cfg(feature = "experimental-inspect")]
2414-
let never = parse_quote!(!); // we need to set a type, let's pick something small, it is overridden by annotation anyway
2426+
let any = parse_quote!(#pyo3_path::Py<#pyo3_path::PyAny>);
24152427
#[cfg(feature = "experimental-inspect")]
24162428
let introspection = FunctionIntrospectionData {
24172429
names: &["__eq__", "__ne__"],
24182430
arguments: vec![FnArg::Regular(RegularArg {
24192431
name: Cow::Owned(format_ident!("other")),
2420-
ty: &never,
2432+
ty: &any,
24212433
from_py_with: None,
24222434
default_value: None,
24232435
option_wrapped_type: None,
2424-
annotation: Some(
2425-
options
2426-
.eq
2427-
.map(|_| PyExpr::from_type(cls.clone(), None))
2428-
.into_iter()
2429-
.chain(options.eq_int.map(|_| PyExpr::builtin("int")))
2430-
.reduce(PyExpr::union)
2431-
.expect("At least one must be defined"),
2432-
),
2436+
annotation: None,
24332437
})],
2434-
returns: parse_quote! { ::std::primitive::bool },
2438+
returns: parse_quote!(::std::primitive::bool),
2439+
is_returning_not_implemented_on_extraction_error: true,
24352440
};
24362441
let richcmp_slot = if options.eq.is_some() {
24372442
generate_protocol_slot(
@@ -2507,6 +2512,7 @@ fn pyclass_richcmp(
25072512
annotation: None,
25082513
})],
25092514
returns: parse_quote! { ::std::primitive::bool },
2515+
is_returning_not_implemented_on_extraction_error: true,
25102516
},
25112517
ctx,
25122518
)?;
@@ -2546,6 +2552,7 @@ fn pyclass_hash(
25462552
names: &["__hash__"],
25472553
arguments: Vec::new(),
25482554
returns: parse_quote! { ::std::primitive::u64 },
2555+
is_returning_not_implemented_on_extraction_error: false,
25492556
},
25502557
ctx,
25512558
)?;
@@ -2632,6 +2639,7 @@ fn pyclass_new_impl<'a>(
26322639
})
26332640
.collect(),
26342641
returns: ty.clone(),
2642+
is_returning_not_implemented_on_extraction_error: false,
26352643
},
26362644
ctx,
26372645
)

pyo3-macros-backend/src/pyfunction.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ pub fn impl_wrap_pyfunction(
411411
func.sig.output.clone(),
412412
empty(),
413413
func.sig.asyncness.is_some(),
414+
false,
414415
get_doc(&func.attrs, None).as_ref(),
415416
None,
416417
);

pyo3-macros-backend/src/pyimpl.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ pub fn impl_methods(
144144
&method.spec,
145145
&meth.attrs,
146146
ty,
147+
method.is_returning_not_implemented_on_extraction_error(),
147148
ctx,
148149
));
149150
match pymethod::gen_py_method(ty, method, &meth.attrs, ctx)? {
@@ -386,6 +387,7 @@ pub fn method_introspection_code(
386387
spec: &FnSpec<'_>,
387388
attrs: &[syn::Attribute],
388389
parent: &syn::Type,
390+
is_returning_not_implemented_on_extraction_error: bool,
389391
ctx: &Ctx,
390392
) -> TokenStream {
391393
let Ctx { pyo3_path, .. } = ctx;
@@ -405,7 +407,13 @@ pub fn method_introspection_code(
405407
// We cant to keep the first argument type, hence this hack
406408
spec.signature.arguments.pop();
407409
spec.signature.python_signature.positional_parameters.pop();
408-
method_introspection_code(&spec, attrs, parent, ctx)
410+
method_introspection_code(
411+
&spec,
412+
attrs,
413+
parent,
414+
is_returning_not_implemented_on_extraction_error,
415+
ctx,
416+
)
409417
})
410418
.collect();
411419
}
@@ -493,6 +501,7 @@ pub fn method_introspection_code(
493501
return_type,
494502
decorators,
495503
spec.asyncness.is_some(),
504+
is_returning_not_implemented_on_extraction_error,
496505
get_doc(attrs, None).as_ref(),
497506
Some(parent),
498507
)

pyo3-macros-backend/src/pymethod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,24 @@ impl<'a> PyMethod<'a> {
208208
spec,
209209
})
210210
}
211+
212+
#[cfg(feature = "experimental-inspect")]
213+
pub fn is_returning_not_implemented_on_extraction_error(&self) -> bool {
214+
match &self.kind {
215+
PyMethodKind::Fn => false,
216+
PyMethodKind::Proto(proto) => match proto {
217+
PyMethodProtoKind::Slot(slot) => {
218+
matches!(slot.extract_error_mode, ExtractErrorMode::NotImplemented)
219+
}
220+
PyMethodProtoKind::SlotFragment(slot) => {
221+
matches!(slot.extract_error_mode, ExtractErrorMode::NotImplemented)
222+
}
223+
PyMethodProtoKind::Call
224+
| PyMethodProtoKind::Traverse
225+
| PyMethodProtoKind::Clear => false,
226+
},
227+
}
228+
}
211229
}
212230

213231
pub fn is_proto_method(name: &str) -> bool {

pytests/noxfile.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,11 @@ def mypy(session: nox.Session):
4848
(Path("pyo3_pytests") / "py.typed").touch()
4949
session.install(".[dev]")
5050

51-
# TODO: remove --disable-error-code", "override" when __eq__ and __ne__ will always take object for input
5251
session.run_always(
5352
"python",
5453
"-m",
5554
"mypy",
5655
"tests",
57-
"--disable-error-code",
58-
"override",
5956
)
6057
# TODO: enable stubtest when previously listed errors will be fixed session.run_always("python", "-m", "mypy.stubtest", "pyo3_pytests")
6158
finally:

0 commit comments

Comments
 (0)