From 4960de37ed0855180bad80f67f98501f9c3d4917 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 12 Apr 2024 22:42:10 +0200 Subject: [PATCH 01/24] feat: support pyclass on tuple enums --- guide/src/class.md | 29 +-- pyo3-macros-backend/src/pyclass.rs | 261 +++++++++++++++++++++----- pytests/src/enums.rs | 30 +++ pytests/tests/test_enums.py | 23 +++ pytests/tests/test_enums_match.py | 19 ++ tests/ui/invalid_pyclass_enum.rs | 6 - tests/ui/invalid_pyclass_enum.stderr | 8 - tests/ui/invalid_pymethod_enum.rs | 16 ++ tests/ui/invalid_pymethod_enum.stderr | 25 +++ 9 files changed, 343 insertions(+), 74 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index ce86ec40e5f..0deb78f377a 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -52,18 +52,23 @@ enum HttpResponse { // ... } -// PyO3 also supports enums with non-unit variants -// These complex enums have sligtly different behavior from the simple enums above -// They are meant to work with instance checks and match statement patterns +/// PyO3 also supports enums with Struct and Tuple variants +/// These complex enums have sligtly different behavior from the simple enums above +/// They are meant to work with instance checks and match statement patterns +/// The variants can be mixed and matched +/// Struct variants have named fields while tuple enums generate generic names for fields in order _0, _1, _2, ... +/// Apart from this both types are functionally identical #[pyclass] enum Shape { Circle { radius: f64 }, - Rectangle { width: f64, height: f64 }, - RegularPolygon { side_count: u32, radius: f64 }, - Nothing {}, + Rectangle{ height : f64, width : f64}, + Square(u32), + Nothing(), } ``` + + The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass`, `Number`, `MyEnum`, `HttpResponse`, and `Shape`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. ### Restrictions @@ -1180,7 +1185,7 @@ enum BadSubclass { An enum is complex if it has any non-unit (struct or tuple) variants. -Currently PyO3 supports only struct variants in a complex enum. Support for unit and tuple variants is planned. +Currently PyO3 supports only struct and tuple variants in a complex enum. Support for unit variants is planned. PyO3 adds a class attribute for each variant, which may be used to construct values and in match patterns. PyO3 also provides getter methods for all fields of each variant. @@ -1190,14 +1195,14 @@ PyO3 adds a class attribute for each variant, which may be used to construct val enum Shape { Circle { radius: f64 }, Rectangle { width: f64, height: f64 }, - RegularPolygon { side_count: u32, radius: f64 }, + RegularPolygon ( u32, f64 ), Nothing { }, } # #[cfg(Py_3_10)] Python::with_gil(|py| { let circle = Shape::Circle { radius: 10.0 }.into_py(py); - let square = Shape::RegularPolygon { side_count: 4, radius: 10.0 }.into_py(py); + let square = Shape::RegularPolygon ( 4, 10.0 ).into_py(py); let cls = py.get_type_bound::(); pyo3::py_run!(py, circle square cls, r#" assert isinstance(circle, cls) @@ -1206,8 +1211,8 @@ Python::with_gil(|py| { assert isinstance(square, cls) assert isinstance(square, cls.RegularPolygon) - assert square.side_count == 4 - assert square.radius == 10.0 + assert square._0 == 4 + assert square._1 == 10.0 def count_vertices(cls, shape): match shape: @@ -1215,7 +1220,7 @@ Python::with_gil(|py| { return 0 case cls.Rectangle(): return 4 - case cls.RegularPolygon(side_count=n): + case cls.RegularPolygon(_0=n): return n case cls.Nothing(): return 0 diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 4e71a711802..6f31e3ba556 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -488,29 +488,29 @@ struct PyClassComplexEnum<'a> { impl<'a> PyClassComplexEnum<'a> { fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result { let witness = enum_ - .variants - .iter() - .find(|variant| !matches!(variant.fields, syn::Fields::Unit)) - .expect("complex enum has a non-unit variant") - .ident - .to_owned(); - + .variants + .iter() + .find(|variant| !matches!(variant.fields, syn::Fields::Unit)) + .expect("complex enum has a non-unit variant") + .ident + .to_owned(); + let extract_variant_data = |variant: &'a mut syn::Variant| -> syn::Result> { use syn::Fields; let ident = &variant.ident; let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?; - + let variant = match &variant.fields { Fields::Unit => { bail_spanned!(variant.span() => format!( - "Unit variant `{ident}` is not yet supported in a complex enum\n\ - = help: change to a struct variant with no fields: `{ident} {{ }}`\n\ - = note: the enum is complex because of non-unit variant `{witness}`", - ident=ident, witness=witness)) - } - Fields::Named(fields) => { - let fields = fields + "Unit variant `{ident}` is not yet supported in a complex enum\n\ + = help: change to a struct variant with no fields: `{ident} {{ }}`\n\ + = note: the enum is complex because of non-unit variant `{witness}`", + ident=ident, witness=witness)) + } + Fields::Named(fields) => { + let fields = fields .named .iter() .map(|field| PyClassEnumVariantNamedField { @@ -519,41 +519,49 @@ impl<'a> PyClassComplexEnum<'a> { span: field.span(), }) .collect(); - + PyClassEnumVariant::Struct(PyClassEnumStructVariant { ident, fields, options, }) } - Fields::Unnamed(_) => { - bail_spanned!(variant.span() => format!( - "Tuple variant `{ident}` is not yet supported in a complex enum\n\ - = help: change to a struct variant with named fields: `{ident} {{ /* fields */ }}`\n\ - = note: the enum is complex because of non-unit variant `{witness}`", - ident=ident, witness=witness)) + Fields::Unnamed(types) => { + let fields = types.unnamed.iter().enumerate().map(|(_i, field)| { + PyClassEnumVariantUnnamedField { + ty: &field.ty, + span: field.span(), + } + }).collect(); + + PyClassEnumVariant::Tuple(PyClassEnumTupleVariant { + ident, + fields, + options, + }) } }; - + Ok(variant) }; - - let ident = &enum_.ident; - - let variants: Vec<_> = enum_ + + let ident = &enum_.ident; + + let variants: Vec<_> = enum_ .variants .iter_mut() .map(extract_variant_data) .collect::>()?; - + Ok(Self { ident, variants }) } } +#[derive(Debug)] enum PyClassEnumVariant<'a> { // TODO(mkovaxx): Unit(PyClassEnumUnitVariant<'a>), Struct(PyClassEnumStructVariant<'a>), - // TODO(mkovaxx): Tuple(PyClassEnumTupleVariant<'a>), + Tuple(PyClassEnumTupleVariant<'a>), } trait EnumVariant { @@ -581,12 +589,14 @@ impl<'a> EnumVariant for PyClassEnumVariant<'a> { fn get_ident(&self) -> &syn::Ident { match self { PyClassEnumVariant::Struct(struct_variant) => struct_variant.ident, + PyClassEnumVariant::Tuple(tuple_variant) => tuple_variant.ident, } } fn get_options(&self) -> &EnumVariantPyO3Options { match self { PyClassEnumVariant::Struct(struct_variant) => &struct_variant.options, + PyClassEnumVariant::Tuple(tuple_variant) => &tuple_variant.options } } } @@ -608,20 +618,35 @@ impl<'a> EnumVariant for PyClassEnumUnitVariant<'a> { } /// A struct variant has named fields +#[derive(Debug)] struct PyClassEnumStructVariant<'a> { ident: &'a syn::Ident, fields: Vec>, options: EnumVariantPyO3Options, } +#[derive(Debug)] +struct PyClassEnumTupleVariant<'a> { + ident: &'a syn::Ident, + fields: Vec>, + options : EnumVariantPyO3Options, +} + +#[derive(Debug)] struct PyClassEnumVariantNamedField<'a> { ident: &'a syn::Ident, ty: &'a syn::Type, span: Span, } +#[derive(Debug)] +struct PyClassEnumVariantUnnamedField<'a> { + ty : &'a syn::Type, + span: Span, +} + /// `#[pyo3()]` options for pyclass enum variants -#[derive(Default)] +#[derive(Default, Debug)] struct EnumVariantPyO3Options { name: Option, constructor: Option, @@ -835,7 +860,7 @@ fn impl_complex_enum( ctx: &Ctx, ) -> Result { let Ctx { pyo3_path } = ctx; - + // Need to rig the enum PyClass options let args = { let mut rigged_args = args.clone(); @@ -845,14 +870,14 @@ fn impl_complex_enum( rigged_args.options.subclass = parse_quote!(subclass); rigged_args }; - + let ctx = &Ctx::new(&args.options.krate); let cls = complex_enum.ident; let variants = complex_enum.variants; let pytypeinfo = impl_pytypeinfo(cls, &args, None, ctx); - + let default_slots = vec![]; - + let impl_builder = PyClassImplsBuilder::new( cls, &args, @@ -860,8 +885,8 @@ fn impl_complex_enum( complex_enum_default_methods( cls, variants - .iter() - .map(|v| (v.get_ident(), v.get_python_name(&args))), + .iter() + .map(|v| (v.get_ident(), v.get_python_name(&args))), ctx, ), default_slots, @@ -913,28 +938,28 @@ fn impl_complex_enum( let mut variant_cls_impls = vec![]; for variant in variants { let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.get_ident()); - + let variant_cls_zst = quote! { #[doc(hidden)] #[allow(non_camel_case_types)] struct #variant_cls; }; variant_cls_zsts.push(variant_cls_zst); - + let variant_args = PyClassArgs { class_kind: PyClassKind::Struct, // TODO(mkovaxx): propagate variant.options options: parse_quote!(extends = #cls, frozen), }; - + let variant_cls_pytypeinfo = impl_pytypeinfo(&variant_cls, &variant_args, None, ctx); variant_cls_pytypeinfos.push(variant_cls_pytypeinfo); - let (variant_cls_impl, field_getters) = impl_complex_enum_variant_cls(cls, &variant, ctx)?; - variant_cls_impls.push(variant_cls_impl); - let variant_new = complex_enum_variant_new(cls, variant, ctx)?; + let (variant_cls_impl, field_getters) = impl_complex_enum_variant_cls(cls, variant, ctx)?; + variant_cls_impls.push(variant_cls_impl); + let pyclass_impl = PyClassImplsBuilder::new( &variant_cls, &variant_args, @@ -943,9 +968,9 @@ fn impl_complex_enum( vec![variant_new], ) .impl_all(ctx)?; - - variant_cls_pyclass_impls.push(pyclass_impl); - } + + variant_cls_pyclass_impls.push(pyclass_impl); +} Ok(quote! { #pytypeinfo @@ -968,19 +993,22 @@ fn impl_complex_enum( fn impl_complex_enum_variant_cls( enum_name: &syn::Ident, - variant: &PyClassEnumVariant<'_>, + variant: PyClassEnumVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec)> { match variant { PyClassEnumVariant::Struct(struct_variant) => { impl_complex_enum_struct_variant_cls(enum_name, struct_variant, ctx) } + PyClassEnumVariant::Tuple(tuple_variant) => { + impl_complex_enum_tuple_variant_cls(enum_name, tuple_variant, ctx) + } } } fn impl_complex_enum_struct_variant_cls( enum_name: &syn::Ident, - variant: &PyClassEnumStructVariant<'_>, + variant: PyClassEnumStructVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec)> { let Ctx { pyo3_path } = ctx; @@ -1031,6 +1059,73 @@ fn impl_complex_enum_struct_variant_cls( Ok((cls_impl, field_getters)) } +fn impl_complex_enum_tuple_variant_cls ( + enum_name: &syn::Ident, + variant: &PyClassEnumTupleVariant<'_>, + ctx: &Ctx, +) -> Result<(TokenStream, Vec)> +{ + let Ctx { pyo3_path } = ctx; + let variant_ident = &variant.ident; + let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); + let variant_cls_type = parse_quote!(#variant_cls); + + + // represents the index of the field + let mut field_names : Vec = vec![]; + let mut fields_with_types : Vec = vec![]; + let mut field_getters : Vec = vec![]; + let mut field_getter_impls : Vec = vec![]; + + for (index, field) in variant.fields.iter().enumerate() { + let field_name = format_ident!("_{}", index); + let field_type = field.ty; + let field_with_type = quote! { #field_name : #field_type }; + + let field_getter = + complex_enum_variant_field_getter(&variant_cls_type, &field_name, field.span, ctx)?; + + // Generate the match arms needed to destructure the tuple and access the specific field + let field_access_tokens: Vec<_> = (0..variant.fields.len()) + .map(|i| if i == index { + quote! { val } + } else { + quote! { _ } + }) + .collect(); + + + let field_getter_impl = quote! { + fn #field_name(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult<#field_type> { + match &*slf.into_super() { + #enum_name::#variant_ident ( #(#field_access_tokens), *) => Ok(val.clone()), + _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), + } + } + }; + + field_names.push(field_name); + fields_with_types.push(field_with_type); + field_getters.push(field_getter); + field_getter_impls.push(field_getter_impl); + } + + let cls_impl = quote! { + #[doc(hidden)] + #[allow(non_snake_case)] + impl #variant_cls { + fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#fields_with_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> { + let base_value = #enum_name::#variant_ident ( #(#field_names,)* ); + #pyo3_path::PyClassInitializer::from(base_value).add_subclass(#variant_cls) + } + + #(#field_getter_impls)* + } + }; + + Ok((cls_impl, field_getters)) +} + fn gen_complex_enum_variant_class_ident(enum_: &syn::Ident, variant: &syn::Ident) -> syn::Ident { format_ident!("{}_{}", enum_, variant) } @@ -1148,6 +1243,9 @@ fn complex_enum_variant_new<'a>( match variant { PyClassEnumVariant::Struct(struct_variant) => { complex_enum_struct_variant_new(cls, struct_variant, ctx) + }, + PyClassEnumVariant::Tuple(tuple_variant) => { + complex_enum_tuple_variant_new(cls, tuple_variant, ctx) } } } @@ -1157,7 +1255,7 @@ fn complex_enum_struct_variant_new<'a>( variant: PyClassEnumStructVariant<'a>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path } = ctx; let variant_cls = format_ident!("{}_{}", cls, variant.ident); let variant_cls_type: syn::Type = parse_quote!(#variant_cls); @@ -1209,6 +1307,73 @@ fn complex_enum_struct_variant_new<'a>( crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx) } +fn complex_enum_tuple_variant_new<'a>( + cls : &'a syn::Ident, + variant: PyClassEnumTupleVariant<'a>, + ctx: &Ctx, +) -> Result { + let Ctx { pyo3_path } = ctx; + + let variant_cls: Ident = format_ident!("{}_{}", cls, variant.ident); + let variant_cls_type : syn::Type = parse_quote!(#variant_cls); + + let arg_py_ident : syn::Ident = parse_quote!(py); + let arg_py_type : syn::Type = parse_quote!(#pyo3_path::Python<'_>); + + let args = { + let mut no_pyo3_attrs = vec![]; + let attrs = crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; + + let mut args = vec![ + // py: Python<'_> + FnArg { + name : &arg_py_ident, + ty: &arg_py_type, + optional : None, + default : None, + py: true, + attrs: attrs.clone(), + is_varargs : false, + is_kwargs : false, + is_cancel_handle : false, + }, + ]; + + for (i, field) in variant.fields.iter().enumerate() { + // ! Warning : This leaks memory. I have no idea how else to do this - is this even bad? + let field_ident = format_ident!("_{}", i); + let boxed_ident = Box::from(field_ident); + let leaky_ident = Box::leak(boxed_ident); + args.push( FnArg { + name : leaky_ident, + ty : field.ty, + optional : None, + default : None, + py : false, + attrs : attrs.clone(), + is_varargs: false, + is_kwargs: false, + is_cancel_handle: false, + }); + } + args + }; + let signature = crate::pyfunction::FunctionSignature::from_arguments(args)?; + let spec = FnSpec { + tp: crate::method::FnType::FnNew, + name : &format_ident!("__pymethod_constructor__"), + python_name: format_ident!("__new__"), + signature, + convention: crate::method::CallingConvention::TpNew, + text_signature: None, + asyncness : None, + unsafety: None, + deprecations: Deprecations::new(ctx), + }; + + crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx) +} + fn complex_enum_variant_field_getter<'a>( variant_cls_type: &'a syn::Type, field_name: &'a syn::Ident, diff --git a/pytests/src/enums.rs b/pytests/src/enums.rs index 68a5fc93dfe..297aba4fd2c 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -8,8 +8,10 @@ use pyo3::{ pub fn enums(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_wrapped(wrap_pyfunction_bound!(do_simple_stuff))?; m.add_wrapped(wrap_pyfunction_bound!(do_complex_stuff))?; + m.add_wrapped(wrap_pyfunction_bound!(do_tuple_stuff))?; Ok(()) } @@ -79,3 +81,31 @@ pub fn do_complex_stuff(thing: &ComplexEnum) -> ComplexEnum { }, } } + +#[pyclass] +pub enum TupleEnum { + Full(i32, f64, bool), + EmptyTuple(), +} + +#[pyfunction] +pub fn do_tuple_stuff(thing: &TupleEnum) -> TupleEnum { + match thing { + TupleEnum::Full(a, b, c) => TupleEnum::Full(*a, *b, *c), + TupleEnum::EmptyTuple() => TupleEnum::EmptyTuple(), + } +} + +#[pyclass] +pub enum MixedComplexEnum { + Nothing {}, + Empty(), +} + +#[pyfunction] +pub fn do_mixed_complex_stuff(thing: &MixedComplexEnum) -> MixedComplexEnum { + match thing { + MixedComplexEnum::Nothing {} => MixedComplexEnum::Nothing{}, + MixedComplexEnum::Empty() => MixedComplexEnum::Empty (), + } +} \ No newline at end of file diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index cd1d7aedaf8..1062567eaa2 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -137,3 +137,26 @@ def test_complex_enum_pyfunction_in_out_desugared_match(variant: enums.ComplexEn assert y == "HELLO" else: assert False + +def test_tuple_enum_variant_constructors(): + tuple_variant = enums.TupleEnum.Full(42, 3.14, False) + assert isinstance(tuple_variant, enums.TupleEnum.Full) + + empty_tuple_variant = enums.TupleEnum.EmptyTuple() + assert isinstance(empty_tuple_variant, enums.TupleEnum.EmptyTuple) + +@pytest.mark.parametrize( + "variant", + [ + enums.TupleEnum.Full(42, 3.14, False), + enums.TupleEnum.EmptyTuple(), + ], +) +def test_tuple_enum_variant_subclasses(variant: enums.TupleEnum): + assert isinstance(variant, enums.TupleEnum) + +def test_tuple_enum_field_getters(): + tuple_variant = enums.TupleEnum.Full(42, 3.14, False) + assert tuple_variant._0 == 42 + assert tuple_variant._1 == 3.14 + assert tuple_variant._2 is False \ No newline at end of file diff --git a/pytests/tests/test_enums_match.py b/pytests/tests/test_enums_match.py index 4d55bbbe351..6a37396e35e 100644 --- a/pytests/tests/test_enums_match.py +++ b/pytests/tests/test_enums_match.py @@ -57,3 +57,22 @@ def test_complex_enum_pyfunction_in_out(variant: enums.ComplexEnum): assert z is True case _: assert False + +@pytest.mark.parametrize( + "variant", + [ + enums.TupleEnum.Full(42, 3.14, True), + enums.TupleEnum.EmptyTuple(), + ], +) +def test_tuple_enum_match_statement(variant: enums.TupleEnum): + match variant: + case enums.TupleEnum.Full(_0=x, _1=y, _2=z): + assert x == 42 + assert y == 3.14 + assert z == True + case enums.TupleEnum.EmptyTuple(): + assert True + case _: + print(variant) + assert False \ No newline at end of file diff --git a/tests/ui/invalid_pyclass_enum.rs b/tests/ui/invalid_pyclass_enum.rs index 116b8968da8..e98010fea32 100644 --- a/tests/ui/invalid_pyclass_enum.rs +++ b/tests/ui/invalid_pyclass_enum.rs @@ -21,12 +21,6 @@ enum NoUnitVariants { UnitVariant, } -#[pyclass] -enum NoTupleVariants { - StructVariant { field: i32 }, - TupleVariant(i32), -} - #[pyclass] enum SimpleNoSignature { #[pyo3(constructor = (a, b))] diff --git a/tests/ui/invalid_pyclass_enum.stderr b/tests/ui/invalid_pyclass_enum.stderr index e9ba9806da8..d25fcaeb6cb 100644 --- a/tests/ui/invalid_pyclass_enum.stderr +++ b/tests/ui/invalid_pyclass_enum.stderr @@ -24,14 +24,6 @@ error: Unit variant `UnitVariant` is not yet supported in a complex enum 21 | UnitVariant, | ^^^^^^^^^^^ -error: Tuple variant `TupleVariant` is not yet supported in a complex enum - = help: change to a struct variant with named fields: `TupleVariant { /* fields */ }` - = note: the enum is complex because of non-unit variant `StructVariant` - --> tests/ui/invalid_pyclass_enum.rs:27:5 - | -27 | TupleVariant(i32), - | ^^^^^^^^^^^^ - error: `constructor` can't be used on a simple enum variant --> tests/ui/invalid_pyclass_enum.rs:32:12 | diff --git a/tests/ui/invalid_pymethod_enum.rs b/tests/ui/invalid_pymethod_enum.rs index 9b596e087ff..5c41d19d4e7 100644 --- a/tests/ui/invalid_pymethod_enum.rs +++ b/tests/ui/invalid_pymethod_enum.rs @@ -16,4 +16,20 @@ impl ComplexEnum { } } +#[pyclass] +enum TupleEnum { + Int(i32), + Str(String), +} + +#[pymethods] +impl TupleEnum { + fn mutate_in_place(&mut self) { + *self = match self { + TupleEnum::Int(int) => TupleEnum::Str(int.to_string()), + TupleEnum::Str(string) => TupleEnum::Int(string.len() as i32), + } + } +} + fn main() {} diff --git a/tests/ui/invalid_pymethod_enum.stderr b/tests/ui/invalid_pymethod_enum.stderr index 6cf6fe89bdf..bc377d2a055 100644 --- a/tests/ui/invalid_pymethod_enum.stderr +++ b/tests/ui/invalid_pymethod_enum.stderr @@ -22,3 +22,28 @@ note: required by a bound in `PyRefMut` | pub struct PyRefMut<'p, T: PyClass> { | ^^^^^^^^^^^^^^ required by this bound in `PyRefMut` = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0271]: type mismatch resolving `::Frozen == False` + --> tests/ui/invalid_pymethod_enum.rs:27:24 + | +27 | fn mutate_in_place(&mut self) { + | ^ expected `False`, found `True` + | +note: required by a bound in `extract_pyclass_ref_mut` + --> src/impl_/extract_argument.rs + | + | pub fn extract_pyclass_ref_mut<'a, 'py: 'a, T: PyClass>( + | ^^^^^^^^^^^^^^ required by this bound in `extract_pyclass_ref_mut` + +error[E0271]: type mismatch resolving `::Frozen == False` + --> tests/ui/invalid_pymethod_enum.rs:25:1 + | +25 | #[pymethods] + | ^^^^^^^^^^^^ expected `False`, found `True` + | +note: required by a bound in `PyRefMut` + --> src/pycell.rs + | + | pub struct PyRefMut<'p, T: PyClass> { + | ^^^^^^^^^^^^^^ required by this bound in `PyRefMut` + = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) From 53c95f698e17309677ea3a679b73cd5184f41676 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 12 Apr 2024 22:43:28 +0200 Subject: [PATCH 02/24] cargo fmt --- pyo3-macros-backend/src/pyclass.rs | 159 +++++++++++++++-------------- pytests/src/enums.rs | 6 +- 2 files changed, 84 insertions(+), 81 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 6f31e3ba556..546f6a9b6e0 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -488,19 +488,19 @@ struct PyClassComplexEnum<'a> { impl<'a> PyClassComplexEnum<'a> { fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result { let witness = enum_ - .variants - .iter() - .find(|variant| !matches!(variant.fields, syn::Fields::Unit)) - .expect("complex enum has a non-unit variant") - .ident - .to_owned(); - + .variants + .iter() + .find(|variant| !matches!(variant.fields, syn::Fields::Unit)) + .expect("complex enum has a non-unit variant") + .ident + .to_owned(); + let extract_variant_data = |variant: &'a mut syn::Variant| -> syn::Result> { use syn::Fields; let ident = &variant.ident; let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?; - + let variant = match &variant.fields { Fields::Unit => { bail_spanned!(variant.span() => format!( @@ -508,9 +508,9 @@ impl<'a> PyClassComplexEnum<'a> { = help: change to a struct variant with no fields: `{ident} {{ }}`\n\ = note: the enum is complex because of non-unit variant `{witness}`", ident=ident, witness=witness)) - } - Fields::Named(fields) => { - let fields = fields + } + Fields::Named(fields) => { + let fields = fields .named .iter() .map(|field| PyClassEnumVariantNamedField { @@ -519,7 +519,7 @@ impl<'a> PyClassComplexEnum<'a> { span: field.span(), }) .collect(); - + PyClassEnumVariant::Struct(PyClassEnumStructVariant { ident, fields, @@ -527,13 +527,16 @@ impl<'a> PyClassComplexEnum<'a> { }) } Fields::Unnamed(types) => { - let fields = types.unnamed.iter().enumerate().map(|(_i, field)| { - PyClassEnumVariantUnnamedField { + let fields = types + .unnamed + .iter() + .enumerate() + .map(|(_i, field)| PyClassEnumVariantUnnamedField { ty: &field.ty, span: field.span(), - } - }).collect(); - + }) + .collect(); + PyClassEnumVariant::Tuple(PyClassEnumTupleVariant { ident, fields, @@ -541,18 +544,18 @@ impl<'a> PyClassComplexEnum<'a> { }) } }; - + Ok(variant) }; - - let ident = &enum_.ident; - - let variants: Vec<_> = enum_ + + let ident = &enum_.ident; + + let variants: Vec<_> = enum_ .variants .iter_mut() .map(extract_variant_data) .collect::>()?; - + Ok(Self { ident, variants }) } } @@ -596,7 +599,7 @@ impl<'a> EnumVariant for PyClassEnumVariant<'a> { fn get_options(&self) -> &EnumVariantPyO3Options { match self { PyClassEnumVariant::Struct(struct_variant) => &struct_variant.options, - PyClassEnumVariant::Tuple(tuple_variant) => &tuple_variant.options + PyClassEnumVariant::Tuple(tuple_variant) => &tuple_variant.options, } } } @@ -629,7 +632,7 @@ struct PyClassEnumStructVariant<'a> { struct PyClassEnumTupleVariant<'a> { ident: &'a syn::Ident, fields: Vec>, - options : EnumVariantPyO3Options, + options: EnumVariantPyO3Options, } #[derive(Debug)] @@ -641,7 +644,7 @@ struct PyClassEnumVariantNamedField<'a> { #[derive(Debug)] struct PyClassEnumVariantUnnamedField<'a> { - ty : &'a syn::Type, + ty: &'a syn::Type, span: Span, } @@ -860,7 +863,7 @@ fn impl_complex_enum( ctx: &Ctx, ) -> Result { let Ctx { pyo3_path } = ctx; - + // Need to rig the enum PyClass options let args = { let mut rigged_args = args.clone(); @@ -870,14 +873,14 @@ fn impl_complex_enum( rigged_args.options.subclass = parse_quote!(subclass); rigged_args }; - + let ctx = &Ctx::new(&args.options.krate); let cls = complex_enum.ident; let variants = complex_enum.variants; let pytypeinfo = impl_pytypeinfo(cls, &args, None, ctx); - + let default_slots = vec![]; - + let impl_builder = PyClassImplsBuilder::new( cls, &args, @@ -885,8 +888,8 @@ fn impl_complex_enum( complex_enum_default_methods( cls, variants - .iter() - .map(|v| (v.get_ident(), v.get_python_name(&args))), + .iter() + .map(|v| (v.get_ident(), v.get_python_name(&args))), ctx, ), default_slots, @@ -938,23 +941,24 @@ fn impl_complex_enum( let mut variant_cls_impls = vec![]; for variant in variants { let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.get_ident()); - + let variant_cls_zst = quote! { #[doc(hidden)] #[allow(non_camel_case_types)] struct #variant_cls; }; variant_cls_zsts.push(variant_cls_zst); - + let variant_args = PyClassArgs { class_kind: PyClassKind::Struct, // TODO(mkovaxx): propagate variant.options options: parse_quote!(extends = #cls, frozen), }; - + let variant_cls_pytypeinfo = impl_pytypeinfo(&variant_cls, &variant_args, None, ctx); variant_cls_pytypeinfos.push(variant_cls_pytypeinfo); + let variant_new = complex_enum_variant_new(cls, variant, ctx)?; let (variant_cls_impl, field_getters) = impl_complex_enum_variant_cls(cls, variant, ctx)?; @@ -968,9 +972,9 @@ fn impl_complex_enum( vec![variant_new], ) .impl_all(ctx)?; - - variant_cls_pyclass_impls.push(pyclass_impl); -} + + variant_cls_pyclass_impls.push(pyclass_impl); + } Ok(quote! { #pytypeinfo @@ -1059,23 +1063,21 @@ fn impl_complex_enum_struct_variant_cls( Ok((cls_impl, field_getters)) } -fn impl_complex_enum_tuple_variant_cls ( +fn impl_complex_enum_tuple_variant_cls( enum_name: &syn::Ident, variant: &PyClassEnumTupleVariant<'_>, ctx: &Ctx, -) -> Result<(TokenStream, Vec)> -{ +) -> Result<(TokenStream, Vec)> { let Ctx { pyo3_path } = ctx; let variant_ident = &variant.ident; let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); let variant_cls_type = parse_quote!(#variant_cls); - // represents the index of the field - let mut field_names : Vec = vec![]; - let mut fields_with_types : Vec = vec![]; - let mut field_getters : Vec = vec![]; - let mut field_getter_impls : Vec = vec![]; + let mut field_names: Vec = vec![]; + let mut fields_with_types: Vec = vec![]; + let mut field_getters: Vec = vec![]; + let mut field_getter_impls: Vec = vec![]; for (index, field) in variant.fields.iter().enumerate() { let field_name = format_ident!("_{}", index); @@ -1083,27 +1085,28 @@ fn impl_complex_enum_tuple_variant_cls ( let field_with_type = quote! { #field_name : #field_type }; let field_getter = - complex_enum_variant_field_getter(&variant_cls_type, &field_name, field.span, ctx)?; + complex_enum_variant_field_getter(&variant_cls_type, &field_name, field.span, ctx)?; // Generate the match arms needed to destructure the tuple and access the specific field let field_access_tokens: Vec<_> = (0..variant.fields.len()) - .map(|i| if i == index { - quote! { val } - } else { - quote! { _ } + .map(|i| { + if i == index { + quote! { val } + } else { + quote! { _ } + } }) .collect(); - let field_getter_impl = quote! { fn #field_name(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult<#field_type> { match &*slf.into_super() { #enum_name::#variant_ident ( #(#field_access_tokens), *) => Ok(val.clone()), _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), } - } + } }; - + field_names.push(field_name); fields_with_types.push(field_with_type); field_getters.push(field_getter); @@ -1243,7 +1246,7 @@ fn complex_enum_variant_new<'a>( match variant { PyClassEnumVariant::Struct(struct_variant) => { complex_enum_struct_variant_new(cls, struct_variant, ctx) - }, + } PyClassEnumVariant::Tuple(tuple_variant) => { complex_enum_tuple_variant_new(cls, tuple_variant, ctx) } @@ -1255,7 +1258,7 @@ fn complex_enum_struct_variant_new<'a>( variant: PyClassEnumStructVariant<'a>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path } = ctx; let variant_cls = format_ident!("{}_{}", cls, variant.ident); let variant_cls_type: syn::Type = parse_quote!(#variant_cls); @@ -1311,14 +1314,14 @@ fn complex_enum_tuple_variant_new<'a>( cls : &'a syn::Ident, variant: PyClassEnumTupleVariant<'a>, ctx: &Ctx, -) -> Result { +) -> Result { let Ctx { pyo3_path } = ctx; - + let variant_cls: Ident = format_ident!("{}_{}", cls, variant.ident); - let variant_cls_type : syn::Type = parse_quote!(#variant_cls); + let variant_cls_type: syn::Type = parse_quote!(#variant_cls); - let arg_py_ident : syn::Ident = parse_quote!(py); - let arg_py_type : syn::Type = parse_quote!(#pyo3_path::Python<'_>); + let arg_py_ident: syn::Ident = parse_quote!(py); + let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>); let args = { let mut no_pyo3_attrs = vec![]; @@ -1327,30 +1330,30 @@ fn complex_enum_tuple_variant_new<'a>( let mut args = vec![ // py: Python<'_> FnArg { - name : &arg_py_ident, + name: &arg_py_ident, ty: &arg_py_type, - optional : None, - default : None, + optional: None, + default: None, py: true, attrs: attrs.clone(), - is_varargs : false, - is_kwargs : false, - is_cancel_handle : false, + is_varargs: false, + is_kwargs: false, + is_cancel_handle: false, }, ]; for (i, field) in variant.fields.iter().enumerate() { // ! Warning : This leaks memory. I have no idea how else to do this - is this even bad? - let field_ident = format_ident!("_{}", i); + let field_ident = format_ident!("_{}", i); let boxed_ident = Box::from(field_ident); let leaky_ident = Box::leak(boxed_ident); - args.push( FnArg { - name : leaky_ident, - ty : field.ty, - optional : None, - default : None, - py : false, - attrs : attrs.clone(), + args.push(FnArg { + name: leaky_ident, + ty: field.ty, + optional: None, + default: None, + py: false, + attrs: attrs.clone(), is_varargs: false, is_kwargs: false, is_cancel_handle: false, @@ -1361,16 +1364,16 @@ fn complex_enum_tuple_variant_new<'a>( let signature = crate::pyfunction::FunctionSignature::from_arguments(args)?; let spec = FnSpec { tp: crate::method::FnType::FnNew, - name : &format_ident!("__pymethod_constructor__"), + name: &format_ident!("__pymethod_constructor__"), python_name: format_ident!("__new__"), signature, convention: crate::method::CallingConvention::TpNew, text_signature: None, - asyncness : None, + asyncness: None, unsafety: None, deprecations: Deprecations::new(ctx), }; - + crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx) } diff --git a/pytests/src/enums.rs b/pytests/src/enums.rs index 297aba4fd2c..059157e9944 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -105,7 +105,7 @@ pub enum MixedComplexEnum { #[pyfunction] pub fn do_mixed_complex_stuff(thing: &MixedComplexEnum) -> MixedComplexEnum { match thing { - MixedComplexEnum::Nothing {} => MixedComplexEnum::Nothing{}, - MixedComplexEnum::Empty() => MixedComplexEnum::Empty (), + MixedComplexEnum::Nothing {} => MixedComplexEnum::Nothing {}, + MixedComplexEnum::Empty() => MixedComplexEnum::Empty(), } -} \ No newline at end of file +} From e051a5fcd9977e11b545e79f67fecdc72486958d Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 12 Apr 2024 22:52:29 +0200 Subject: [PATCH 03/24] changelog --- newsfragments/4072.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/4072.added.md diff --git a/newsfragments/4072.added.md b/newsfragments/4072.added.md new file mode 100644 index 00000000000..23207c849d8 --- /dev/null +++ b/newsfragments/4072.added.md @@ -0,0 +1 @@ +Support `#[pyclass]` on enums that have tuple variants. \ No newline at end of file From eaf5265eaa6cbffb1c0af7b6c2e909c110496259 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 12 Apr 2024 22:59:30 +0200 Subject: [PATCH 04/24] ruff format --- pytests/tests/test_enums.py | 5 ++++- pytests/tests/test_enums_match.py | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index 1062567eaa2..4aba756cd88 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -138,6 +138,7 @@ def test_complex_enum_pyfunction_in_out_desugared_match(variant: enums.ComplexEn else: assert False + def test_tuple_enum_variant_constructors(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) assert isinstance(tuple_variant, enums.TupleEnum.Full) @@ -145,6 +146,7 @@ def test_tuple_enum_variant_constructors(): empty_tuple_variant = enums.TupleEnum.EmptyTuple() assert isinstance(empty_tuple_variant, enums.TupleEnum.EmptyTuple) + @pytest.mark.parametrize( "variant", [ @@ -155,8 +157,9 @@ def test_tuple_enum_variant_constructors(): def test_tuple_enum_variant_subclasses(variant: enums.TupleEnum): assert isinstance(variant, enums.TupleEnum) + def test_tuple_enum_field_getters(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) assert tuple_variant._0 == 42 assert tuple_variant._1 == 3.14 - assert tuple_variant._2 is False \ No newline at end of file + assert tuple_variant._2 is False diff --git a/pytests/tests/test_enums_match.py b/pytests/tests/test_enums_match.py index 6a37396e35e..2b99c39dfa1 100644 --- a/pytests/tests/test_enums_match.py +++ b/pytests/tests/test_enums_match.py @@ -58,6 +58,7 @@ def test_complex_enum_pyfunction_in_out(variant: enums.ComplexEnum): case _: assert False + @pytest.mark.parametrize( "variant", [ @@ -70,9 +71,9 @@ def test_tuple_enum_match_statement(variant: enums.TupleEnum): case enums.TupleEnum.Full(_0=x, _1=y, _2=z): assert x == 42 assert y == 3.14 - assert z == True + assert z is True case enums.TupleEnum.EmptyTuple(): assert True case _: print(variant) - assert False \ No newline at end of file + assert False From 51d434ff9d764e7eb8b936f3dd270f84a9d6061b Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Wed, 1 May 2024 14:48:01 +0200 Subject: [PATCH 05/24] rebase with adaptation for FnArg refactor --- pyo3-macros-backend/src/pyclass.rs | 31 +++++++++++------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 546f6a9b6e0..b2efe47c633 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1329,17 +1329,10 @@ fn complex_enum_tuple_variant_new<'a>( let mut args = vec![ // py: Python<'_> - FnArg { + FnArg::Py (PyArg { name: &arg_py_ident, ty: &arg_py_type, - optional: None, - default: None, - py: true, - attrs: attrs.clone(), - is_varargs: false, - is_kwargs: false, - is_cancel_handle: false, - }, + }), ]; for (i, field) in variant.fields.iter().enumerate() { @@ -1347,17 +1340,15 @@ fn complex_enum_tuple_variant_new<'a>( let field_ident = format_ident!("_{}", i); let boxed_ident = Box::from(field_ident); let leaky_ident = Box::leak(boxed_ident); - args.push(FnArg { - name: leaky_ident, - ty: field.ty, - optional: None, - default: None, - py: false, - attrs: attrs.clone(), - is_varargs: false, - is_kwargs: false, - is_cancel_handle: false, - }); + args.push(FnArg::Regular( + RegularArg { + name: leaky_ident, + ty: field.ty, + from_py_with: None, + default_value: None, + option_wrapped_type: None, + }, + )); } args }; From 9c540ed155e986ba5d890edb7f6d9a824f18e02c Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Fri, 19 Apr 2024 11:09:33 +0100 Subject: [PATCH 06/24] fix class.md from pr comments --- guide/src/class.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index 0deb78f377a..11650b4ecef 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -52,23 +52,21 @@ enum HttpResponse { // ... } -/// PyO3 also supports enums with Struct and Tuple variants -/// These complex enums have sligtly different behavior from the simple enums above -/// They are meant to work with instance checks and match statement patterns -/// The variants can be mixed and matched -/// Struct variants have named fields while tuple enums generate generic names for fields in order _0, _1, _2, ... -/// Apart from this both types are functionally identical +// PyO3 also supports enums with Struct and Tuple variants +// These complex enums have sligtly different behavior from the simple enums above +// They are meant to work with instance checks and match statement patterns +// The variants can be mixed and matched +// Struct variants have named fields while tuple enums generate generic names for fields in order _0, _1, _2, ... +// Apart from this both types are functionally identical #[pyclass] enum Shape { Circle { radius: f64 }, - Rectangle{ height : f64, width : f64}, - Square(u32), + Rectangle { height: f64, width: f64 }, + RegularPolygon(u32, f64), Nothing(), } ``` - - The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass`, `Number`, `MyEnum`, `HttpResponse`, and `Shape`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. ### Restrictions @@ -1195,14 +1193,14 @@ PyO3 adds a class attribute for each variant, which may be used to construct val enum Shape { Circle { radius: f64 }, Rectangle { width: f64, height: f64 }, - RegularPolygon ( u32, f64 ), + RegularPolygon(u32, f64), Nothing { }, } # #[cfg(Py_3_10)] Python::with_gil(|py| { let circle = Shape::Circle { radius: 10.0 }.into_py(py); - let square = Shape::RegularPolygon ( 4, 10.0 ).into_py(py); + let square = Shape::RegularPolygon(4, 10.0).into_py(py); let cls = py.get_type_bound::(); pyo3::py_run!(py, circle square cls, r#" assert isinstance(circle, cls) From 7b06fe598adb5217e31d9aecf1a8edbf2f2a1b10 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Fri, 19 Apr 2024 10:50:00 +0100 Subject: [PATCH 07/24] add enum tuple variant getitem implementation --- pyo3-macros-backend/src/pyclass.rs | 82 +++++++++++++++++++++++++++ pyo3-macros-backend/src/pymethod.rs | 88 +++++++++++++++++++++++++++++ pytests/tests/test_enums.py | 4 ++ 3 files changed, 174 insertions(+) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index b2efe47c633..0f05809f7d7 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1113,6 +1113,84 @@ fn impl_complex_enum_tuple_variant_cls( field_getter_impls.push(field_getter_impl); } + let num_fields = variant.fields.len(); + let match_arms: Vec<_> = (0..num_fields) + .map(|i| { + let field_access = format!("tup.{}", i); + quote! { + #i => Ok(Box::new(#field_access.clone())) + } + }) + .collect(); + + let matcher = if num_fields > 0 { + quote! { + let tup = &*slf.into_super(); + match key { + #( #match_arms, )* + _ => Err(pyo3::exceptions::PyIndexError::new_err("tuple index out of range")), + } + } + } else { + quote! { + Err(#pyo3_path::exceptions::PyIndexError::new_err("tuple index out of range")) + } + }; + + let getitem_method = { + let arg_py_ident: syn::Ident = parse_quote!(py); + let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>); + let mut no_pyo3_attrs = vec![]; + let attrs = crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; + let key_name = format_ident!("key"); + let arg_key_type: syn::Type = parse_quote!(usize); + let args = vec![ + // py: Python<'_> + FnArg { + name: &arg_py_ident, + ty: &arg_py_type, + optional: None, + default: None, + py: true, + attrs: attrs.clone(), + is_varargs: false, + is_kwargs: false, + is_cancel_handle: false, + }, + FnArg { + name: &key_name, + ty: &arg_key_type, + optional: None, + default: None, + py: false, + attrs: attrs.clone(), + is_varargs: false, + is_kwargs: false, + is_cancel_handle: false, + }, + ]; + let signature = crate::pyfunction::FunctionSignature::from_arguments(args)?; + let func_self_type: syn::Type = parse_quote!(Box); + let self_type = crate::method::SelfType::TryFromBoundRef(func_self_type.span()); + let spec = FnSpec { + tp: crate::method::FnType::Fn(self_type.clone()), + name: &format_ident!("getitem"), + python_name: format_ident!("__getitem__"), + signature, + convention: crate::method::CallingConvention::Varargs, + text_signature: None, + asyncness: None, + unsafety: None, + deprecations: Deprecations::new(ctx), + }; + // This is the function I'm struggling with + // If this is removed, the code compiles fine and all existing tests pass + // but obviously __getitem__ isn't implemented on Python side + crate::pymethod::impl_py_getitem_def(&variant_cls_type, &self_type, spec, ctx)? + }; + + field_getters.push(getitem_method); + let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] @@ -1122,6 +1200,10 @@ fn impl_complex_enum_tuple_variant_cls( #pyo3_path::PyClassInitializer::from(base_value).add_subclass(#variant_cls) } + fn getitem(slf: #pyo3_path::PyRef, key: usize) -> #pyo3_path::PyResult> { + #matcher + } + #(#field_getter_impls)* } }; diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 208735f2619..569d860b70b 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -739,6 +739,94 @@ fn impl_call_getter( Ok(fncall) } +pub fn impl_py_getitem_def( + cls: &syn::Type, + _self_type: &SelfType, + spec: FnSpec<'_>, + ctx: &Ctx, +) -> Result { + + // + // APPROACH 1 + // This is what the #[pymethods] macro uses + // + // Not used... + // let method = { + // let kind = PyMethodKind::from_name("__getitem__"); + // let method_name = spec.python_name.to_string(); + // PyMethod { + // kind, + // method_name, + // spec, + // } + // }; + let doc = crate::get_doc(&[], None); + Ok(impl_py_method_def(cls, &spec, &doc, None, ctx)?) + + // APPROACH 2 + // APPROACH 2-a + // use the `generate_method_body`... + // let Ctx { pyo3_path } = ctx; + // let python_name = spec.null_terminated_python_name(); + // let mut holders = Holders::new(); + // let arguments: Vec = vec![Ty::Int]; + // let extract_error_mode = ExtractErrorMode::Raise; + // let body = generate_method_body( + // cls, + // spec, + // &arguments, + // extract_error_mode, + // &mut holders, + // None, + // ctx, + // )?; + + // APPROACH 2-b + // Modify code lifted from `impl_py_getter_def` + // let body = { + // let slf = self_type.receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx); + // let name = &spec.name; + // let fncall = quote!(#cls::#name(#slf, py, key)); + // quote! { + // #pyo3_path::callback::convert(py, #fncall, key) + // } + // }; + // + // let wrapper_ident = format_ident!("__pymethod_getitem_wrapper__"); + // let cfg_attrs = TokenStream::new(); + // let init_holders = holders.init_holders(ctx); + // let check_gil_refs = holders.check_gil_refs(); + // let associated_method = quote! { + // #cfg_attrs + // unsafe fn #wrapper_ident( + // py: #pyo3_path::Python<'_>, + // _slf: *mut #pyo3_path::ffi::PyObject, + // key: #pyo3_path::ffi::PyObject, + // ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { + // #init_holders + // let result = #body; + // #check_gil_refs + // result + // } + // }; + // + // let method_def = quote! { + // #cfg_attrs + // #pyo3_path::class::PyMethodDefType::Getter( + // #pyo3_path::class::PyMethodDef::new( + // #python_name, + // #pyo3_path::impl_::pymethods::PyGetter(#cls::#wrapper_ident), + // #doc + // ) + // ) + // }; + // + // Ok(MethodAndMethodDef { + // associated_method, + // method_def, + // }) +} + // Used here for PropertyType::Function, used in pyclass for descriptors. pub fn impl_py_getter_def( cls: &syn::Type, diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index 4aba756cd88..4de10ad2cc9 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -163,3 +163,7 @@ def test_tuple_enum_field_getters(): assert tuple_variant._0 == 42 assert tuple_variant._1 == 3.14 assert tuple_variant._2 is False + +def test_tuple_enum_index_getter(): + tuple_variant = enums.TupleEnum.Full(42, 3.14, False) + assert tuple_variant[0] == 42 From 3c487757d31d4f1f2da9ed4fc7647a2b919670e4 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 29 Apr 2024 10:55:14 +0100 Subject: [PATCH 08/24] fmt --- pyo3-macros-backend/src/pymethod.rs | 1 - pytests/tests/test_enums.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 569d860b70b..9620add809f 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -745,7 +745,6 @@ pub fn impl_py_getitem_def( spec: FnSpec<'_>, ctx: &Ctx, ) -> Result { - // // APPROACH 1 // This is what the #[pymethods] macro uses diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index 4de10ad2cc9..f50456e88bd 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -164,6 +164,7 @@ def test_tuple_enum_field_getters(): assert tuple_variant._1 == 3.14 assert tuple_variant._2 is False + def test_tuple_enum_index_getter(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) assert tuple_variant[0] == 42 From a2f582cc9881826a7be36a21a614180f7e4e4705 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Wed, 1 May 2024 20:08:59 +0200 Subject: [PATCH 09/24] progress toward getitem and len impl on derive pyclass for complex enum tuple --- pyo3-macros-backend/src/pyclass.rs | 167 ++++++++++++++-------------- pyo3-macros-backend/src/pymethod.rs | 119 ++++++-------------- 2 files changed, 120 insertions(+), 166 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 0f05809f7d7..bc8d869bdcc 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1063,21 +1063,18 @@ fn impl_complex_enum_struct_variant_cls( Ok((cls_impl, field_getters)) } -fn impl_complex_enum_tuple_variant_cls( - enum_name: &syn::Ident, - variant: &PyClassEnumTupleVariant<'_>, +fn impl_complex_enum_tuple_variant_match_arms( ctx: &Ctx, -) -> Result<(TokenStream, Vec)> { + variant: &PyClassEnumTupleVariant<'_>, + enum_name: &syn::Ident, + variant_cls_type: &syn::Type, + variant_ident: &&Ident, + field_names: &mut Vec, + fields_with_types: &mut Vec, + field_getters: &mut Vec, + field_getter_impls: &mut Vec, +) -> Result<()> { let Ctx { pyo3_path } = ctx; - let variant_ident = &variant.ident; - let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); - let variant_cls_type = parse_quote!(#variant_cls); - - // represents the index of the field - let mut field_names: Vec = vec![]; - let mut fields_with_types: Vec = vec![]; - let mut field_getters: Vec = vec![]; - let mut field_getter_impls: Vec = vec![]; for (index, field) in variant.fields.iter().enumerate() { let field_name = format_ident!("_{}", index); @@ -1113,20 +1110,69 @@ fn impl_complex_enum_tuple_variant_cls( field_getter_impls.push(field_getter_impl); } + Ok(()) +} + +fn impl_complex_enum_tuple_variant_cls( + enum_name: &syn::Ident, + variant: &PyClassEnumTupleVariant<'_>, + ctx: &Ctx, +) -> Result<(TokenStream, Vec)> { + let Ctx { pyo3_path } = ctx; + let variant_ident = &variant.ident; + let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); + let variant_cls_type = parse_quote!(#variant_cls); + + // represents the index of the field + let mut field_names: Vec = vec![]; + let mut fields_with_types: Vec = vec![]; + let mut field_getters: Vec = vec![]; + let mut field_getter_impls: Vec = vec![]; + + impl_complex_enum_tuple_variant_match_arms( + ctx, + variant, + enum_name, + &variant_cls_type, + &variant_ident, + &mut field_names, + &mut fields_with_types, + &mut field_getters, + &mut field_getter_impls, + )?; + let num_fields = variant.fields.len(); + + let mut len_signature : syn::Signature = syn::parse_quote!(fn __len__(slf: PyRef) -> PyResult); + let _len_method = crate::pymethod::impl_py_len_def(&variant_cls_type, ctx, &mut len_signature)?; + + let len_method_impl = quote! { + fn __len__(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult { + Ok(#num_fields) + } + }; + + let options = PyFunctionOptions::default(); + let generated_len_method = crate::pymethod::gen_py_method(&variant_cls_type, &mut len_signature, &mut Vec::new(), options, ctx); + + let match_arms: Vec<_> = (0..num_fields) .map(|i| { - let field_access = format!("tup.{}", i); + let field_access = format_ident!("_{}", i); quote! { - #i => Ok(Box::new(#field_access.clone())) - } + #i => Ok( + #pyo3_path::IntoPy::into_py( + #variant_cls::#field_access(slf)? + , unsafe { pyo3::Python::assume_gil_acquired() }) + ) + + } }) .collect(); let matcher = if num_fields > 0 { quote! { - let tup = &*slf.into_super(); - match key { + match idx { #( #match_arms, )* _ => Err(pyo3::exceptions::PyIndexError::new_err("tuple index out of range")), } @@ -1136,61 +1182,16 @@ fn impl_complex_enum_tuple_variant_cls( Err(#pyo3_path::exceptions::PyIndexError::new_err("tuple index out of range")) } }; + + let mut get_item_signature : syn::Signature = syn::parse_quote!(fn __getitem__(slf: PyRef, idx: usize) -> PyResult); + let _getitem_method = crate::pymethod::impl_py_getitem_def(&variant_cls_type, ctx, &mut get_item_signature)?; - let getitem_method = { - let arg_py_ident: syn::Ident = parse_quote!(py); - let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>); - let mut no_pyo3_attrs = vec![]; - let attrs = crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; - let key_name = format_ident!("key"); - let arg_key_type: syn::Type = parse_quote!(usize); - let args = vec![ - // py: Python<'_> - FnArg { - name: &arg_py_ident, - ty: &arg_py_type, - optional: None, - default: None, - py: true, - attrs: attrs.clone(), - is_varargs: false, - is_kwargs: false, - is_cancel_handle: false, - }, - FnArg { - name: &key_name, - ty: &arg_key_type, - optional: None, - default: None, - py: false, - attrs: attrs.clone(), - is_varargs: false, - is_kwargs: false, - is_cancel_handle: false, - }, - ]; - let signature = crate::pyfunction::FunctionSignature::from_arguments(args)?; - let func_self_type: syn::Type = parse_quote!(Box); - let self_type = crate::method::SelfType::TryFromBoundRef(func_self_type.span()); - let spec = FnSpec { - tp: crate::method::FnType::Fn(self_type.clone()), - name: &format_ident!("getitem"), - python_name: format_ident!("__getitem__"), - signature, - convention: crate::method::CallingConvention::Varargs, - text_signature: None, - asyncness: None, - unsafety: None, - deprecations: Deprecations::new(ctx), - }; - // This is the function I'm struggling with - // If this is removed, the code compiles fine and all existing tests pass - // but obviously __getitem__ isn't implemented on Python side - crate::pymethod::impl_py_getitem_def(&variant_cls_type, &self_type, spec, ctx)? + let get_item_method_impl = quote! { + fn __getitem__(slf: #pyo3_path::PyRef, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::PyObject> { + #matcher + } }; - field_getters.push(getitem_method); - let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] @@ -1200,9 +1201,9 @@ fn impl_complex_enum_tuple_variant_cls( #pyo3_path::PyClassInitializer::from(base_value).add_subclass(#variant_cls) } - fn getitem(slf: #pyo3_path::PyRef, key: usize) -> #pyo3_path::PyResult> { - #matcher - } + #len_method_impl + + #get_item_method_impl #(#field_getter_impls)* } @@ -1407,11 +1408,11 @@ fn complex_enum_tuple_variant_new<'a>( let args = { let mut no_pyo3_attrs = vec![]; - let attrs = crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; + let _attrs = crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; let mut args = vec![ // py: Python<'_> - FnArg::Py (PyArg { + FnArg::Py(PyArg { name: &arg_py_ident, ty: &arg_py_type, }), @@ -1422,15 +1423,13 @@ fn complex_enum_tuple_variant_new<'a>( let field_ident = format_ident!("_{}", i); let boxed_ident = Box::from(field_ident); let leaky_ident = Box::leak(boxed_ident); - args.push(FnArg::Regular( - RegularArg { - name: leaky_ident, - ty: field.ty, - from_py_with: None, - default_value: None, - option_wrapped_type: None, - }, - )); + args.push(FnArg::Regular(RegularArg { + name: leaky_ident, + ty: field.ty, + from_py_with: None, + default_value: None, + option_wrapped_type: None, + })); } args }; diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 9620add809f..b7c4724e070 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -739,91 +739,46 @@ fn impl_call_getter( Ok(fncall) } +pub fn impl_py_len_def( + cls: &syn::Type, + ctx: &Ctx, + signature : &mut syn::Signature +) -> Result { + + let len_spec = FnSpec::parse( + signature, + &mut Vec::new(), + PyFunctionOptions::default(), + ctx, + )?; + + Ok(__LEN__.generate_type_slot( + &cls, + &len_spec, + "__len__", + ctx, + )?) +} + pub fn impl_py_getitem_def( cls: &syn::Type, - _self_type: &SelfType, - spec: FnSpec<'_>, ctx: &Ctx, -) -> Result { - // - // APPROACH 1 - // This is what the #[pymethods] macro uses - // - // Not used... - // let method = { - // let kind = PyMethodKind::from_name("__getitem__"); - // let method_name = spec.python_name.to_string(); - // PyMethod { - // kind, - // method_name, - // spec, - // } - // }; - let doc = crate::get_doc(&[], None); - Ok(impl_py_method_def(cls, &spec, &doc, None, ctx)?) - - // APPROACH 2 - // APPROACH 2-a - // use the `generate_method_body`... - // let Ctx { pyo3_path } = ctx; - // let python_name = spec.null_terminated_python_name(); - // let mut holders = Holders::new(); - // let arguments: Vec = vec![Ty::Int]; - // let extract_error_mode = ExtractErrorMode::Raise; - // let body = generate_method_body( - // cls, - // spec, - // &arguments, - // extract_error_mode, - // &mut holders, - // None, - // ctx, - // )?; - - // APPROACH 2-b - // Modify code lifted from `impl_py_getter_def` - // let body = { - // let slf = self_type.receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx); - // let name = &spec.name; - // let fncall = quote!(#cls::#name(#slf, py, key)); - // quote! { - // #pyo3_path::callback::convert(py, #fncall, key) - // } - // }; - // - // let wrapper_ident = format_ident!("__pymethod_getitem_wrapper__"); - // let cfg_attrs = TokenStream::new(); - // let init_holders = holders.init_holders(ctx); - // let check_gil_refs = holders.check_gil_refs(); - // let associated_method = quote! { - // #cfg_attrs - // unsafe fn #wrapper_ident( - // py: #pyo3_path::Python<'_>, - // _slf: *mut #pyo3_path::ffi::PyObject, - // key: #pyo3_path::ffi::PyObject, - // ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { - // #init_holders - // let result = #body; - // #check_gil_refs - // result - // } - // }; - // - // let method_def = quote! { - // #cfg_attrs - // #pyo3_path::class::PyMethodDefType::Getter( - // #pyo3_path::class::PyMethodDef::new( - // #python_name, - // #pyo3_path::impl_::pymethods::PyGetter(#cls::#wrapper_ident), - // #doc - // ) - // ) - // }; - // - // Ok(MethodAndMethodDef { - // associated_method, - // method_def, - // }) + signature : &mut syn::Signature, +) -> Result { + + let get_item_spec = FnSpec::parse( + signature, + &mut Vec::new(), + PyFunctionOptions::default(), + ctx, + )?; + + Ok(__GETITEM__.generate_type_slot( + &cls, + &get_item_spec, + "__getitem__", + ctx, + )?) } // Used here for PropertyType::Function, used in pyclass for descriptors. From d72a893e6fd8393c90acaf91160960f42c2cefb4 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Wed, 1 May 2024 20:46:00 +0200 Subject: [PATCH 10/24] working getitem and len slots for complex tuple enum pyclass derivation --- pyo3-macros-backend/src/pyclass.rs | 53 +++++++++++++++++------------ pyo3-macros-backend/src/pymethod.rs | 22 +++--------- pytests/tests/test_enums.py | 1 + 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index bc8d869bdcc..3d79598638e 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -961,15 +961,18 @@ fn impl_complex_enum( let variant_new = complex_enum_variant_new(cls, variant, ctx)?; - let (variant_cls_impl, field_getters) = impl_complex_enum_variant_cls(cls, variant, ctx)?; + let (variant_cls_impl, field_getters, mut slots) = + impl_complex_enum_variant_cls(cls, variant, ctx)?; variant_cls_impls.push(variant_cls_impl); + slots.push(variant_new); + let pyclass_impl = PyClassImplsBuilder::new( &variant_cls, &variant_args, methods_type, field_getters, - vec![variant_new], + slots, ) .impl_all(ctx)?; @@ -999,7 +1002,7 @@ fn impl_complex_enum_variant_cls( enum_name: &syn::Ident, variant: PyClassEnumVariant<'_>, ctx: &Ctx, -) -> Result<(TokenStream, Vec)> { +) -> Result<(TokenStream, Vec, Vec)> { match variant { PyClassEnumVariant::Struct(struct_variant) => { impl_complex_enum_struct_variant_cls(enum_name, struct_variant, ctx) @@ -1014,7 +1017,7 @@ fn impl_complex_enum_struct_variant_cls( enum_name: &syn::Ident, variant: PyClassEnumStructVariant<'_>, ctx: &Ctx, -) -> Result<(TokenStream, Vec)> { +) -> Result<(TokenStream, Vec, Vec)> { let Ctx { pyo3_path } = ctx; let variant_ident = &variant.ident; let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); @@ -1060,7 +1063,7 @@ fn impl_complex_enum_struct_variant_cls( } }; - Ok((cls_impl, field_getters)) + Ok((cls_impl, field_getters, Vec::new())) } fn impl_complex_enum_tuple_variant_match_arms( @@ -1117,12 +1120,14 @@ fn impl_complex_enum_tuple_variant_cls( enum_name: &syn::Ident, variant: &PyClassEnumTupleVariant<'_>, ctx: &Ctx, -) -> Result<(TokenStream, Vec)> { +) -> Result<(TokenStream, Vec, Vec)> { let Ctx { pyo3_path } = ctx; let variant_ident = &variant.ident; let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); let variant_cls_type = parse_quote!(#variant_cls); + let mut slots = vec![]; + // represents the index of the field let mut field_names: Vec = vec![]; let mut fields_with_types: Vec = vec![]; @@ -1143,8 +1148,11 @@ fn impl_complex_enum_tuple_variant_cls( let num_fields = variant.fields.len(); - let mut len_signature : syn::Signature = syn::parse_quote!(fn __len__(slf: PyRef) -> PyResult); - let _len_method = crate::pymethod::impl_py_len_def(&variant_cls_type, ctx, &mut len_signature)?; + let mut len_signature: syn::Signature = + syn::parse_quote!(fn __len__(slf: PyRef) -> PyResult); + let variant_len = crate::pymethod::impl_py_len_def(&variant_cls_type, ctx, &mut len_signature)?; + + slots.push(variant_len); let len_method_impl = quote! { fn __len__(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult { @@ -1152,21 +1160,24 @@ fn impl_complex_enum_tuple_variant_cls( } }; - let options = PyFunctionOptions::default(); - let generated_len_method = crate::pymethod::gen_py_method(&variant_cls_type, &mut len_signature, &mut Vec::new(), options, ctx); + let mut get_item_signature: syn::Signature = + syn::parse_quote!(fn __getitem__(slf: PyRef, idx: usize) -> PyResult); + let variant_len = + crate::pymethod::impl_py_getitem_def(&variant_cls_type, ctx, &mut get_item_signature)?; + slots.push(variant_len); let match_arms: Vec<_> = (0..num_fields) .map(|i| { let field_access = format_ident!("_{}", i); quote! { - #i => Ok( - #pyo3_path::IntoPy::into_py( - #variant_cls::#field_access(slf)? - , unsafe { pyo3::Python::assume_gil_acquired() }) - ) - - } + #i => Ok( + #pyo3_path::IntoPy::into_py( + #variant_cls::#field_access(slf)? + , unsafe { pyo3::Python::assume_gil_acquired() }) + ) + + } }) .collect(); @@ -1182,9 +1193,6 @@ fn impl_complex_enum_tuple_variant_cls( Err(#pyo3_path::exceptions::PyIndexError::new_err("tuple index out of range")) } }; - - let mut get_item_signature : syn::Signature = syn::parse_quote!(fn __getitem__(slf: PyRef, idx: usize) -> PyResult); - let _getitem_method = crate::pymethod::impl_py_getitem_def(&variant_cls_type, ctx, &mut get_item_signature)?; let get_item_method_impl = quote! { fn __getitem__(slf: #pyo3_path::PyRef, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::PyObject> { @@ -1209,7 +1217,7 @@ fn impl_complex_enum_tuple_variant_cls( } }; - Ok((cls_impl, field_getters)) + Ok((cls_impl, field_getters, slots)) } fn gen_complex_enum_variant_class_ident(enum_: &syn::Ident, variant: &syn::Ident) -> syn::Ident { @@ -1408,7 +1416,8 @@ fn complex_enum_tuple_variant_new<'a>( let args = { let mut no_pyo3_attrs = vec![]; - let _attrs = crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; + let _attrs = + crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; let mut args = vec![ // py: Python<'_> diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index b7c4724e070..35a182223d7 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -742,9 +742,8 @@ fn impl_call_getter( pub fn impl_py_len_def( cls: &syn::Type, ctx: &Ctx, - signature : &mut syn::Signature + signature: &mut syn::Signature, ) -> Result { - let len_spec = FnSpec::parse( signature, &mut Vec::new(), @@ -752,33 +751,22 @@ pub fn impl_py_len_def( ctx, )?; - Ok(__LEN__.generate_type_slot( - &cls, - &len_spec, - "__len__", - ctx, - )?) + Ok(__LEN__.generate_type_slot(&cls, &len_spec, "__len__", ctx)?) } pub fn impl_py_getitem_def( cls: &syn::Type, ctx: &Ctx, - signature : &mut syn::Signature, + signature: &mut syn::Signature, ) -> Result { - let get_item_spec = FnSpec::parse( signature, &mut Vec::new(), PyFunctionOptions::default(), ctx, )?; - - Ok(__GETITEM__.generate_type_slot( - &cls, - &get_item_spec, - "__getitem__", - ctx, - )?) + + Ok(__GETITEM__.generate_type_slot(&cls, &get_item_spec, "__getitem__", ctx)?) } // Used here for PropertyType::Function, used in pyclass for descriptors. diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index f50456e88bd..05f74e4c6a3 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -167,4 +167,5 @@ def test_tuple_enum_field_getters(): def test_tuple_enum_index_getter(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) + assert tuple_variant.__len__() == 3 assert tuple_variant[0] == 42 From 13aa7a00b58eff63b7b8a02b90d213876aeaf23d Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Wed, 1 May 2024 21:07:43 +0200 Subject: [PATCH 11/24] refactor code generation --- pyo3-macros-backend/src/pyclass.rs | 123 +++++++++++++++++++---------- 1 file changed, 83 insertions(+), 40 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 3d79598638e..9d398fc7613 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1066,7 +1066,7 @@ fn impl_complex_enum_struct_variant_cls( Ok((cls_impl, field_getters, Vec::new())) } -fn impl_complex_enum_tuple_variant_match_arms( +fn impl_complex_enum_tuple_variant_field_getters( ctx: &Ctx, variant: &PyClassEnumTupleVariant<'_>, enum_name: &syn::Ident, @@ -1074,11 +1074,12 @@ fn impl_complex_enum_tuple_variant_match_arms( variant_ident: &&Ident, field_names: &mut Vec, fields_with_types: &mut Vec, - field_getters: &mut Vec, - field_getter_impls: &mut Vec, -) -> Result<()> { +) -> Result<(Vec, Vec)> { let Ctx { pyo3_path } = ctx; + let mut field_getters = vec![]; + let mut field_getter_impls = vec![]; + for (index, field) in variant.fields.iter().enumerate() { let field_name = format_ident!("_{}", index); let field_type = field.ty; @@ -1113,60 +1114,48 @@ fn impl_complex_enum_tuple_variant_match_arms( field_getter_impls.push(field_getter_impl); } - Ok(()) + Ok((field_getters, field_getter_impls)) } -fn impl_complex_enum_tuple_variant_cls( - enum_name: &syn::Ident, - variant: &PyClassEnumTupleVariant<'_>, +fn impl_complex_enum_tuple_variant_len( ctx: &Ctx, -) -> Result<(TokenStream, Vec, Vec)> { + _variant: &PyClassEnumTupleVariant<'_>, + _enum_name: &syn::Ident, + variant_cls_type: &syn::Type, + _variant_ident: &&Ident, + num_fields: usize, +) -> Result<(MethodAndSlotDef, TokenStream)> { let Ctx { pyo3_path } = ctx; - let variant_ident = &variant.ident; - let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); - let variant_cls_type = parse_quote!(#variant_cls); - - let mut slots = vec![]; - - // represents the index of the field - let mut field_names: Vec = vec![]; - let mut fields_with_types: Vec = vec![]; - let mut field_getters: Vec = vec![]; - let mut field_getter_impls: Vec = vec![]; - - impl_complex_enum_tuple_variant_match_arms( - ctx, - variant, - enum_name, - &variant_cls_type, - &variant_ident, - &mut field_names, - &mut fields_with_types, - &mut field_getters, - &mut field_getter_impls, - )?; - - let num_fields = variant.fields.len(); let mut len_signature: syn::Signature = syn::parse_quote!(fn __len__(slf: PyRef) -> PyResult); let variant_len = crate::pymethod::impl_py_len_def(&variant_cls_type, ctx, &mut len_signature)?; - slots.push(variant_len); - let len_method_impl = quote! { fn __len__(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult { Ok(#num_fields) } }; + Ok((variant_len, len_method_impl)) +} + +fn impl_complex_enum_tuple_variant_getitem( + ctx: &Ctx, + _variant: &PyClassEnumTupleVariant<'_>, + _enum_name: &syn::Ident, + variant_cls: &syn::Ident, + variant_cls_type: &syn::Type, + _variant_ident: &&Ident, + num_fields: usize, +) -> Result<(MethodAndSlotDef, TokenStream)> { + let Ctx { pyo3_path } = ctx; + let mut get_item_signature: syn::Signature = syn::parse_quote!(fn __getitem__(slf: PyRef, idx: usize) -> PyResult); - let variant_len = + let variant_getitem = crate::pymethod::impl_py_getitem_def(&variant_cls_type, ctx, &mut get_item_signature)?; - slots.push(variant_len); - let match_arms: Vec<_> = (0..num_fields) .map(|i| { let field_access = format_ident!("_{}", i); @@ -1200,6 +1189,60 @@ fn impl_complex_enum_tuple_variant_cls( } }; + Ok((variant_getitem, get_item_method_impl)) +} + +fn impl_complex_enum_tuple_variant_cls( + enum_name: &syn::Ident, + variant: &PyClassEnumTupleVariant<'_>, + ctx: &Ctx, +) -> Result<(TokenStream, Vec, Vec)> { + let Ctx { pyo3_path } = ctx; + let variant_ident = &variant.ident; + let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); + let variant_cls_type = parse_quote!(#variant_cls); + + let mut slots = vec![]; + + // represents the index of the field + let mut field_names: Vec = vec![]; + let mut fields_with_types: Vec = vec![]; + + let (field_getters, field_getter_impls) = impl_complex_enum_tuple_variant_field_getters( + ctx, + variant, + enum_name, + &variant_cls_type, + &variant_ident, + &mut field_names, + &mut fields_with_types, + )?; + + let num_fields = variant.fields.len(); + + let (variant_len, len_method_impl) = impl_complex_enum_tuple_variant_len( + ctx, + variant, + enum_name, + &variant_cls_type, + &variant_ident, + num_fields, + )?; + + slots.push(variant_len); + + let (variant_getitem, getitem_method_impl) = impl_complex_enum_tuple_variant_getitem( + ctx, + variant, + enum_name, + &variant_cls, + &variant_cls_type, + &variant_ident, + num_fields, + )?; + + slots.push(variant_getitem); + let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] @@ -1211,7 +1254,7 @@ fn impl_complex_enum_tuple_variant_cls( #len_method_impl - #get_item_method_impl + #getitem_method_impl #(#field_getter_impls)* } From 47cd6ce9cce8c474f46e50d87c52dc0efc0fdb2a Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 3 May 2024 22:58:33 +0200 Subject: [PATCH 12/24] address PR concerns - take py from function argument on get_item - make more general slot def implementation - remove unnecessary function arguments - add testcases for uncovered cases including future feature match_args --- pyo3-macros-backend/src/pyclass.rs | 49 ++++++++--------------- pyo3-macros-backend/src/pymethod.rs | 36 +++++++++-------- pytests/src/enums.rs | 6 ++- pytests/tests/test_enums.py | 19 ++++++++- pytests/tests/test_enums_match.py | 60 +++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 53 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 9d398fc7613..a9aae869cb4 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1119,43 +1119,33 @@ fn impl_complex_enum_tuple_variant_field_getters( fn impl_complex_enum_tuple_variant_len( ctx: &Ctx, - _variant: &PyClassEnumTupleVariant<'_>, - _enum_name: &syn::Ident, + variant_cls_type: &syn::Type, - _variant_ident: &&Ident, num_fields: usize, -) -> Result<(MethodAndSlotDef, TokenStream)> { +) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> { let Ctx { pyo3_path } = ctx; - let mut len_signature: syn::Signature = - syn::parse_quote!(fn __len__(slf: PyRef) -> PyResult); - let variant_len = crate::pymethod::impl_py_len_def(&variant_cls_type, ctx, &mut len_signature)?; - let len_method_impl = quote! { fn __len__(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult { Ok(#num_fields) } }; + let mut len_method_impl : syn ::ImplItemFn = syn::parse2(len_method_impl).unwrap(); + + let variant_len = crate::pymethod::impl_py_slot_def(&variant_cls_type, ctx, &mut len_method_impl.sig)?; + Ok((variant_len, len_method_impl)) } fn impl_complex_enum_tuple_variant_getitem( ctx: &Ctx, - _variant: &PyClassEnumTupleVariant<'_>, - _enum_name: &syn::Ident, variant_cls: &syn::Ident, variant_cls_type: &syn::Type, - _variant_ident: &&Ident, num_fields: usize, -) -> Result<(MethodAndSlotDef, TokenStream)> { +) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> { let Ctx { pyo3_path } = ctx; - let mut get_item_signature: syn::Signature = - syn::parse_quote!(fn __getitem__(slf: PyRef, idx: usize) -> PyResult); - let variant_getitem = - crate::pymethod::impl_py_getitem_def(&variant_cls_type, ctx, &mut get_item_signature)?; - let match_arms: Vec<_> = (0..num_fields) .map(|i| { let field_access = format_ident!("_{}", i); @@ -1163,25 +1153,21 @@ fn impl_complex_enum_tuple_variant_getitem( #i => Ok( #pyo3_path::IntoPy::into_py( #variant_cls::#field_access(slf)? - , unsafe { pyo3::Python::assume_gil_acquired() }) + , py) ) } }) .collect(); - let matcher = if num_fields > 0 { + let matcher = quote! { + let py = slf.py(); match idx { #( #match_arms, )* _ => Err(pyo3::exceptions::PyIndexError::new_err("tuple index out of range")), } - } - } else { - quote! { - Err(#pyo3_path::exceptions::PyIndexError::new_err("tuple index out of range")) - } - }; + }; let get_item_method_impl = quote! { fn __getitem__(slf: #pyo3_path::PyRef, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::PyObject> { @@ -1189,6 +1175,9 @@ fn impl_complex_enum_tuple_variant_getitem( } }; + let mut get_item_method_impl : syn ::ImplItemFn = syn::parse2(get_item_method_impl).unwrap(); + let variant_getitem = crate::pymethod::impl_py_slot_def(&variant_cls_type, ctx, &mut get_item_method_impl.sig)?; + Ok((variant_getitem, get_item_method_impl)) } @@ -1222,10 +1211,7 @@ fn impl_complex_enum_tuple_variant_cls( let (variant_len, len_method_impl) = impl_complex_enum_tuple_variant_len( ctx, - variant, - enum_name, &variant_cls_type, - &variant_ident, num_fields, )?; @@ -1233,11 +1219,8 @@ fn impl_complex_enum_tuple_variant_cls( let (variant_getitem, getitem_method_impl) = impl_complex_enum_tuple_variant_getitem( ctx, - variant, - enum_name, &variant_cls, &variant_cls_type, - &variant_ident, num_fields, )?; @@ -1463,7 +1446,6 @@ fn complex_enum_tuple_variant_new<'a>( crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; let mut args = vec![ - // py: Python<'_> FnArg::Py(PyArg { name: &arg_py_ident, ty: &arg_py_type, @@ -1471,7 +1453,8 @@ fn complex_enum_tuple_variant_new<'a>( ]; for (i, field) in variant.fields.iter().enumerate() { - // ! Warning : This leaks memory. I have no idea how else to do this - is this even bad? + // TODO (@newcomertv): Open tracking issue and PR to modify FnArg to take a Cow + // ! Warning : This leaks memory. This is a temporary solution until we can modify FnArg to take a Cow let field_ident = format_ident!("_{}", i); let boxed_ident = Box::from(field_ident); let leaky_ident = Box::leak(boxed_ident); diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 35a182223d7..ee5ee0d4e2e 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -739,34 +739,36 @@ fn impl_call_getter( Ok(fncall) } -pub fn impl_py_len_def( + +pub fn impl_py_slot_def( cls: &syn::Type, ctx: &Ctx, - signature: &mut syn::Signature, + signature: &mut syn::Signature ) -> Result { - let len_spec = FnSpec::parse( + + let spec = FnSpec::parse( signature, &mut Vec::new(), PyFunctionOptions::default(), ctx, )?; - Ok(__LEN__.generate_type_slot(&cls, &len_spec, "__len__", ctx)?) -} + let method_name = spec.python_name.to_string(); -pub fn impl_py_getitem_def( - cls: &syn::Type, - ctx: &Ctx, - signature: &mut syn::Signature, -) -> Result { - let get_item_spec = FnSpec::parse( - signature, - &mut Vec::new(), - PyFunctionOptions::default(), - ctx, - )?; + let slot = match PyMethodKind::from_name(&method_name) { + PyMethodKind::Proto(proto_kind) => { + ensure_no_forbidden_protocol_attributes(&proto_kind, &spec, &method_name)?; + match proto_kind { + PyMethodProtoKind::Slot(slot_def) => { + slot_def + } + _ => bail_spanned!(signature.span() => "Only slot methods are supported in #[pyslot]"), + } + }, + _ => bail_spanned!(signature.span() => "Only slot methods are supported in #[pyslot]"), + }; - Ok(__GETITEM__.generate_type_slot(&cls, &get_item_spec, "__getitem__", ctx)?) + Ok(slot.generate_type_slot(&cls, &spec, &method_name, ctx)?) } // Used here for PropertyType::Function, used in pyclass for descriptors. diff --git a/pytests/src/enums.rs b/pytests/src/enums.rs index 059157e9944..ba0561a62d9 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -9,9 +9,11 @@ pub fn enums(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_wrapped(wrap_pyfunction_bound!(do_simple_stuff))?; m.add_wrapped(wrap_pyfunction_bound!(do_complex_stuff))?; m.add_wrapped(wrap_pyfunction_bound!(do_tuple_stuff))?; + m.add_wrapped(wrap_pyfunction_bound!(do_mixed_complex_stuff))?; Ok(()) } @@ -105,7 +107,7 @@ pub enum MixedComplexEnum { #[pyfunction] pub fn do_mixed_complex_stuff(thing: &MixedComplexEnum) -> MixedComplexEnum { match thing { - MixedComplexEnum::Nothing {} => MixedComplexEnum::Nothing {}, - MixedComplexEnum::Empty() => MixedComplexEnum::Empty(), + MixedComplexEnum::Nothing {} => MixedComplexEnum::Empty (), + MixedComplexEnum::Empty() => MixedComplexEnum::Nothing{}, } } diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index 05f74e4c6a3..321ec180d52 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -167,5 +167,22 @@ def test_tuple_enum_field_getters(): def test_tuple_enum_index_getter(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) - assert tuple_variant.__len__() == 3 + assert len(tuple_variant) == 3 assert tuple_variant[0] == 42 + + +@pytest.mark.parametrize( + "variant", + [enums.MixedComplexEnum.Nothing()], +) +def test_mixed_complex_enum_pyfunction_instance_nothing(variant: enums.MixedComplexEnum): + assert isinstance(variant, enums.MixedComplexEnum.Nothing) + assert isinstance(enums.do_mixed_complex_stuff(variant), enums.MixedComplexEnum.Empty) + +@pytest.mark.parametrize( + "variant", + [enums.MixedComplexEnum.Empty()], +) +def test_mixed_complex_enum_pyfunction_instance_empty(variant: enums.MixedComplexEnum): + assert isinstance(variant, enums.MixedComplexEnum.Empty) + assert isinstance(enums.do_mixed_complex_stuff(variant), enums.MixedComplexEnum.Nothing) diff --git a/pytests/tests/test_enums_match.py b/pytests/tests/test_enums_match.py index 2b99c39dfa1..be97fc3eb24 100644 --- a/pytests/tests/test_enums_match.py +++ b/pytests/tests/test_enums_match.py @@ -58,6 +58,19 @@ def test_complex_enum_pyfunction_in_out(variant: enums.ComplexEnum): case _: assert False +@pytest.mark.parametrize( + "variant", + [ + enums.ComplexEnum.MultiFieldStruct(42, 3.14, True), + ], +) +@pytest.mark.skip(reason="__match_args__ is not supported for struct enums yet. TODO : Open an issue") +def test_complex_enum_partial_match(variant: enums.ComplexEnum): + match variant: + case enums.ComplexEnum.MultiFieldStruct(a): + assert a == 42 + case _: + assert False @pytest.mark.parametrize( "variant", @@ -77,3 +90,50 @@ def test_tuple_enum_match_statement(variant: enums.TupleEnum): case _: print(variant) assert False + +@pytest.mark.parametrize( + "variant", + [ + enums.TupleEnum.Full(42, 3.14, True), + ], +) +@pytest.mark.skip(reason="__match_args__ is not supported for tuple enums yet. TODO : Open an issue") +def test_tuple_enum_match_match_args(variant : enums.TupleEnum): + match variant: + case enums.TupleEnum.Full(x, y, z): + assert x == 42 + assert y == 3.14 + assert z is True + assert True + case _: + assert False + +@pytest.mark.parametrize( + "variant", + [ + enums.TupleEnum.Full(42, 3.14, True), + ], +) +@pytest.mark.skip(reason="__match_args__ is not supported for tuple enums yet. TODO : Open an issue") +def test_tuple_enum_partial_match(variant : enums.TupleEnum): + match variant: + case enums.TupleEnum.Full(a): + assert a == 42 + case _: + assert False + +@pytest.mark.parametrize( + "variant", + [ + enums.MixedComplexEnum.Nothing(), + enums.MixedComplexEnum.Empty(), + ] +) +def test_mixed_complex_enum_match_statement(variant: enums.MixedComplexEnum): + match variant: + case enums.MixedComplexEnum.Nothing(): + assert True + case enums.MixedComplexEnum.Empty(): + assert True + case _: + assert False From 07710d34d753774c99c0dad7cab117339256f3dc Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Sat, 4 May 2024 12:23:58 +0200 Subject: [PATCH 13/24] add tracking issue --- pyo3-macros-backend/src/pyclass.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index a9aae869cb4..c5d6b6f3a0c 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1453,7 +1453,7 @@ fn complex_enum_tuple_variant_new<'a>( ]; for (i, field) in variant.fields.iter().enumerate() { - // TODO (@newcomertv): Open tracking issue and PR to modify FnArg to take a Cow + // TODO : Tracking issue for Cow in FnArg : #4156 // ! Warning : This leaks memory. This is a temporary solution until we can modify FnArg to take a Cow let field_ident = format_ident!("_{}", i); let boxed_ident = Box::from(field_ident); From 94da194101b160ad5befb457f8f69674b6496853 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Sat, 4 May 2024 12:24:55 +0200 Subject: [PATCH 14/24] fmt --- pyo3-macros-backend/src/pyclass.rs | 50 ++++++++++++----------------- pyo3-macros-backend/src/pymethod.rs | 14 ++++---- pytests/src/enums.rs | 4 +-- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index c5d6b6f3a0c..3b1f156967e 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1131,9 +1131,10 @@ fn impl_complex_enum_tuple_variant_len( } }; - let mut len_method_impl : syn ::ImplItemFn = syn::parse2(len_method_impl).unwrap(); + let mut len_method_impl: syn::ImplItemFn = syn::parse2(len_method_impl).unwrap(); - let variant_len = crate::pymethod::impl_py_slot_def(&variant_cls_type, ctx, &mut len_method_impl.sig)?; + let variant_len = + crate::pymethod::impl_py_slot_def(&variant_cls_type, ctx, &mut len_method_impl.sig)?; Ok((variant_len, len_method_impl)) } @@ -1160,14 +1161,13 @@ fn impl_complex_enum_tuple_variant_getitem( }) .collect(); - let matcher = - quote! { - let py = slf.py(); - match idx { - #( #match_arms, )* - _ => Err(pyo3::exceptions::PyIndexError::new_err("tuple index out of range")), - } - }; + let matcher = quote! { + let py = slf.py(); + match idx { + #( #match_arms, )* + _ => Err(pyo3::exceptions::PyIndexError::new_err("tuple index out of range")), + } + }; let get_item_method_impl = quote! { fn __getitem__(slf: #pyo3_path::PyRef, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::PyObject> { @@ -1175,8 +1175,9 @@ fn impl_complex_enum_tuple_variant_getitem( } }; - let mut get_item_method_impl : syn ::ImplItemFn = syn::parse2(get_item_method_impl).unwrap(); - let variant_getitem = crate::pymethod::impl_py_slot_def(&variant_cls_type, ctx, &mut get_item_method_impl.sig)?; + let mut get_item_method_impl: syn::ImplItemFn = syn::parse2(get_item_method_impl).unwrap(); + let variant_getitem = + crate::pymethod::impl_py_slot_def(&variant_cls_type, ctx, &mut get_item_method_impl.sig)?; Ok((variant_getitem, get_item_method_impl)) } @@ -1209,20 +1210,13 @@ fn impl_complex_enum_tuple_variant_cls( let num_fields = variant.fields.len(); - let (variant_len, len_method_impl) = impl_complex_enum_tuple_variant_len( - ctx, - &variant_cls_type, - num_fields, - )?; + let (variant_len, len_method_impl) = + impl_complex_enum_tuple_variant_len(ctx, &variant_cls_type, num_fields)?; slots.push(variant_len); - let (variant_getitem, getitem_method_impl) = impl_complex_enum_tuple_variant_getitem( - ctx, - &variant_cls, - &variant_cls_type, - num_fields, - )?; + let (variant_getitem, getitem_method_impl) = + impl_complex_enum_tuple_variant_getitem(ctx, &variant_cls, &variant_cls_type, num_fields)?; slots.push(variant_getitem); @@ -1445,12 +1439,10 @@ fn complex_enum_tuple_variant_new<'a>( let _attrs = crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; - let mut args = vec![ - FnArg::Py(PyArg { - name: &arg_py_ident, - ty: &arg_py_type, - }), - ]; + let mut args = vec![FnArg::Py(PyArg { + name: &arg_py_ident, + ty: &arg_py_type, + })]; for (i, field) in variant.fields.iter().enumerate() { // TODO : Tracking issue for Cow in FnArg : #4156 diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index ee5ee0d4e2e..32180d11c4a 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -739,13 +739,11 @@ fn impl_call_getter( Ok(fncall) } - pub fn impl_py_slot_def( cls: &syn::Type, ctx: &Ctx, - signature: &mut syn::Signature + signature: &mut syn::Signature, ) -> Result { - let spec = FnSpec::parse( signature, &mut Vec::new(), @@ -759,13 +757,13 @@ pub fn impl_py_slot_def( PyMethodKind::Proto(proto_kind) => { ensure_no_forbidden_protocol_attributes(&proto_kind, &spec, &method_name)?; match proto_kind { - PyMethodProtoKind::Slot(slot_def) => { - slot_def + PyMethodProtoKind::Slot(slot_def) => slot_def, + _ => { + bail_spanned!(signature.span() => "Only slot methods are supported in #[pyslot]") } - _ => bail_spanned!(signature.span() => "Only slot methods are supported in #[pyslot]"), } - }, - _ => bail_spanned!(signature.span() => "Only slot methods are supported in #[pyslot]"), + } + _ => bail_spanned!(signature.span() => "Only slot methods are supported in #[pyslot]"), }; Ok(slot.generate_type_slot(&cls, &spec, &method_name, ctx)?) diff --git a/pytests/src/enums.rs b/pytests/src/enums.rs index ba0561a62d9..d6566a8931b 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -107,7 +107,7 @@ pub enum MixedComplexEnum { #[pyfunction] pub fn do_mixed_complex_stuff(thing: &MixedComplexEnum) -> MixedComplexEnum { match thing { - MixedComplexEnum::Nothing {} => MixedComplexEnum::Empty (), - MixedComplexEnum::Empty() => MixedComplexEnum::Nothing{}, + MixedComplexEnum::Nothing {} => MixedComplexEnum::Empty(), + MixedComplexEnum::Empty() => MixedComplexEnum::Nothing {}, } } From 1921898872cb3254a506cd6e25ced81b9dc71adc Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Sat, 4 May 2024 12:37:28 +0200 Subject: [PATCH 15/24] ruff --- pytests/tests/test_enums.py | 13 ++++++++++--- pytests/tests/test_enums_match.py | 25 ++++++++++++++++--------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index 321ec180d52..a7feb181ca2 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -175,9 +175,14 @@ def test_tuple_enum_index_getter(): "variant", [enums.MixedComplexEnum.Nothing()], ) -def test_mixed_complex_enum_pyfunction_instance_nothing(variant: enums.MixedComplexEnum): +def test_mixed_complex_enum_pyfunction_instance_nothing( + variant: enums.MixedComplexEnum, +): assert isinstance(variant, enums.MixedComplexEnum.Nothing) - assert isinstance(enums.do_mixed_complex_stuff(variant), enums.MixedComplexEnum.Empty) + assert isinstance( + enums.do_mixed_complex_stuff(variant), enums.MixedComplexEnum.Empty + ) + @pytest.mark.parametrize( "variant", @@ -185,4 +190,6 @@ def test_mixed_complex_enum_pyfunction_instance_nothing(variant: enums.MixedComp ) def test_mixed_complex_enum_pyfunction_instance_empty(variant: enums.MixedComplexEnum): assert isinstance(variant, enums.MixedComplexEnum.Empty) - assert isinstance(enums.do_mixed_complex_stuff(variant), enums.MixedComplexEnum.Nothing) + assert isinstance( + enums.do_mixed_complex_stuff(variant), enums.MixedComplexEnum.Nothing + ) diff --git a/pytests/tests/test_enums_match.py b/pytests/tests/test_enums_match.py index be97fc3eb24..01c5a4352ed 100644 --- a/pytests/tests/test_enums_match.py +++ b/pytests/tests/test_enums_match.py @@ -58,13 +58,16 @@ def test_complex_enum_pyfunction_in_out(variant: enums.ComplexEnum): case _: assert False + @pytest.mark.parametrize( "variant", [ enums.ComplexEnum.MultiFieldStruct(42, 3.14, True), ], ) -@pytest.mark.skip(reason="__match_args__ is not supported for struct enums yet. TODO : Open an issue") +@pytest.mark.skip( + reason="__match_args__ is not supported for struct enums yet. TODO : Open an issue" +) def test_complex_enum_partial_match(variant: enums.ComplexEnum): match variant: case enums.ComplexEnum.MultiFieldStruct(a): @@ -72,6 +75,7 @@ def test_complex_enum_partial_match(variant: enums.ComplexEnum): case _: assert False + @pytest.mark.parametrize( "variant", [ @@ -91,14 +95,15 @@ def test_tuple_enum_match_statement(variant: enums.TupleEnum): print(variant) assert False + @pytest.mark.parametrize( "variant", [ enums.TupleEnum.Full(42, 3.14, True), ], ) -@pytest.mark.skip(reason="__match_args__ is not supported for tuple enums yet. TODO : Open an issue") -def test_tuple_enum_match_match_args(variant : enums.TupleEnum): +@pytest.mark.skip(reason="__match_args__ is not supported for tuple enums yet.") +def test_tuple_enum_match_match_args(variant: enums.TupleEnum): match variant: case enums.TupleEnum.Full(x, y, z): assert x == 42 @@ -108,26 +113,28 @@ def test_tuple_enum_match_match_args(variant : enums.TupleEnum): case _: assert False + @pytest.mark.parametrize( "variant", [ enums.TupleEnum.Full(42, 3.14, True), ], ) -@pytest.mark.skip(reason="__match_args__ is not supported for tuple enums yet. TODO : Open an issue") -def test_tuple_enum_partial_match(variant : enums.TupleEnum): +@pytest.mark.skip(reason="__match_args__ is not supported for tuple enums yet.") +def test_tuple_enum_partial_match(variant: enums.TupleEnum): match variant: case enums.TupleEnum.Full(a): assert a == 42 case _: assert False + @pytest.mark.parametrize( - "variant", + "variant", [ - enums.MixedComplexEnum.Nothing(), - enums.MixedComplexEnum.Empty(), - ] + enums.MixedComplexEnum.Nothing(), + enums.MixedComplexEnum.Empty(), + ], ) def test_mixed_complex_enum_match_statement(variant: enums.MixedComplexEnum): match variant: From 41a33eac3caa6cf71c86bb7d04a4d534ea31a849 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Wed, 8 May 2024 19:43:54 +0200 Subject: [PATCH 16/24] remove me --- pytests/src/enums.rs | 812 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 811 insertions(+), 1 deletion(-) diff --git a/pytests/src/enums.rs b/pytests/src/enums.rs index d6566a8931b..b6e5d41d635 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -84,11 +84,821 @@ pub fn do_complex_stuff(thing: &ComplexEnum) -> ComplexEnum { } } -#[pyclass] pub enum TupleEnum { Full(i32, f64, bool), EmptyTuple(), } +#[allow(deprecated)] +unsafe impl ::pyo3::type_object::HasPyGilRef for TupleEnum { + type AsRefTarget = ::pyo3::PyCell; +} +unsafe impl ::pyo3::type_object::PyTypeInfo for TupleEnum { + const NAME: &'static str = "TupleEnum"; + const MODULE: ::std::option::Option<&'static str> = ::core::option::Option::None; + #[inline] + fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { + use ::pyo3::prelude::PyTypeMethods; + ::lazy_type_object() + .get_or_init(py) + .as_type_ptr() + } +} +impl ::pyo3::PyClass for TupleEnum { + type Frozen = ::pyo3::pyclass::boolean_struct::True; +} +impl<'a, 'py> ::pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a TupleEnum { + type Holder = ::std::option::Option<::pyo3::PyRef<'py, TupleEnum>>; + #[inline] + fn extract( + obj: &'a ::pyo3::Bound<'py, ::pyo3::PyAny>, + holder: &'a mut Self::Holder, + ) -> ::pyo3::PyResult { + ::pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder) + } +} +impl ::pyo3::IntoPy<::pyo3::PyObject> for TupleEnum { + fn into_py(self, py: ::pyo3::Python) -> ::pyo3::PyObject { + match self { + TupleEnum::Full { .. } => { + let pyclass_init = + ::pyo3::PyClassInitializer::from(self).add_subclass(TupleEnum_Full); + let variant_value = ::pyo3::Py::new(py, pyclass_init).unwrap(); + ::pyo3::IntoPy::into_py(variant_value, py) + } + TupleEnum::EmptyTuple { .. } => { + let pyclass_init = + ::pyo3::PyClassInitializer::from(self).add_subclass(TupleEnum_EmptyTuple); + let variant_value = ::pyo3::Py::new(py, pyclass_init).unwrap(); + ::pyo3::IntoPy::into_py(variant_value, py) + } + } + } +} +impl ::pyo3::impl_::pyclass::PyClassImpl for TupleEnum { + const IS_BASETYPE: bool = true; + const IS_SUBCLASS: bool = false; + const IS_MAPPING: bool = false; + const IS_SEQUENCE: bool = false; + type BaseType = ::pyo3::PyAny; + type ThreadChecker = ::pyo3::impl_::pyclass::SendablePyClass; + type PyClassMutability = << :: pyo3 :: + PyAny as :: pyo3 :: impl_ :: pyclass :: PyClassBaseType > :: + PyClassMutability as :: pyo3 :: impl_ :: pycell :: PyClassMutability > :: + ImmutableChild; + type Dict = ::pyo3::impl_::pyclass::PyClassDummySlot; + type WeakRef = ::pyo3::impl_::pyclass::PyClassDummySlot; + type BaseNativeType = ::pyo3::PyAny; + fn items_iter() -> ::pyo3::impl_::pyclass::PyClassItemsIter { + use ::pyo3::impl_::pyclass::*; + let collector = PyClassImplCollector::::new(); + static INTRINSIC_ITEMS: PyClassItems = PyClassItems { + methods: &[ + ::pyo3::class::PyMethodDefType::ClassAttribute({ + ::pyo3::class::PyClassAttributeDef::new( + { "Full\0" }, + ::pyo3::impl_::pymethods::PyClassAttributeFactory( + TupleEnum::__pymethod_variant_cls_Full__, + ), + ) + }), + ::pyo3::class::PyMethodDefType::ClassAttribute({ + ::pyo3::class::PyClassAttributeDef::new( + { "EmptyTuple\0" }, + ::pyo3::impl_::pymethods::PyClassAttributeFactory( + TupleEnum::__pymethod_variant_cls_EmptyTuple__, + ), + ) + }), + ], + slots: &[], + }; + PyClassItemsIter::new(&INTRINSIC_ITEMS, collector.py_methods()) + } + fn doc(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<&'static ::std::ffi::CStr> { + use ::pyo3::impl_::pyclass::*; + static DOC: ::pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = + ::pyo3::sync::GILOnceCell::new(); + DOC.get_or_try_init(py, || { + let collector = PyClassImplCollector::::new(); + build_pyclass_doc( + ::NAME, + "\0", + collector.new_text_signature(), + ) + }) + .map(::std::ops::Deref::deref) + } + fn lazy_type_object() -> &'static ::pyo3::impl_::pyclass::LazyTypeObject { + use ::pyo3::impl_::pyclass::LazyTypeObject; + static TYPE_OBJECT: LazyTypeObject = LazyTypeObject::new(); + &TYPE_OBJECT + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +impl TupleEnum { + fn __pymethod_variant_cls_Full__(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<::pyo3::PyObject> { + ::std::result::Result::Ok(py.get_type_bound::().into_any().unbind()) + } + fn __pymethod_variant_cls_EmptyTuple__( + py: ::pyo3::Python<'_>, + ) -> ::pyo3::PyResult<::pyo3::PyObject> { + ::std::result::Result::Ok( + py.get_type_bound::() + .into_any() + .unbind(), + ) + } +} +impl TupleEnum { + #[doc(hidden)] + pub const _PYO3_DEF: ::pyo3::impl_::pymodule::AddClassToModule = + ::pyo3::impl_::pymodule::AddClassToModule::new(); +} +#[doc(hidden)] +#[allow(non_snake_case)] +impl TupleEnum {} +#[doc(hidden)] +#[allow(non_camel_case_types)] +struct TupleEnum_Full; +#[doc(hidden)] +#[allow(non_camel_case_types)] +struct TupleEnum_EmptyTuple; +#[allow(deprecated)] +unsafe impl ::pyo3::type_object::HasPyGilRef for TupleEnum_Full { + type AsRefTarget = ::pyo3::PyCell; +} +unsafe impl ::pyo3::type_object::PyTypeInfo for TupleEnum_Full { + const NAME: &'static str = "TupleEnum_Full"; + const MODULE: ::std::option::Option<&'static str> = ::core::option::Option::None; + #[inline] + fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { + use ::pyo3::prelude::PyTypeMethods; + ::lazy_type_object() + .get_or_init(py) + .as_type_ptr() + } +} +#[allow(deprecated)] +unsafe impl ::pyo3::type_object::HasPyGilRef for TupleEnum_EmptyTuple { + type AsRefTarget = ::pyo3::PyCell; +} +unsafe impl ::pyo3::type_object::PyTypeInfo for TupleEnum_EmptyTuple { + const NAME: &'static str = "TupleEnum_EmptyTuple"; + const MODULE: ::std::option::Option<&'static str> = ::core::option::Option::None; + #[inline] + fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { + use ::pyo3::prelude::PyTypeMethods; + ::lazy_type_object() + .get_or_init(py) + .as_type_ptr() + } +} +impl ::pyo3::PyClass for TupleEnum_Full { + type Frozen = ::pyo3::pyclass::boolean_struct::True; +} +impl<'a, 'py> ::pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a TupleEnum_Full { + type Holder = ::std::option::Option<::pyo3::PyRef<'py, TupleEnum_Full>>; + #[inline] + fn extract( + obj: &'a ::pyo3::Bound<'py, ::pyo3::PyAny>, + holder: &'a mut Self::Holder, + ) -> ::pyo3::PyResult { + ::pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder) + } +} +impl ::pyo3::impl_::pyclass::PyClassImpl for TupleEnum_Full { + const IS_BASETYPE: bool = false; + const IS_SUBCLASS: bool = true; + const IS_MAPPING: bool = false; + const IS_SEQUENCE: bool = false; + type BaseType = TupleEnum; + type ThreadChecker = ::pyo3::impl_::pyclass::SendablePyClass; + type PyClassMutability = << TupleEnum + as :: pyo3 :: impl_ :: pyclass :: PyClassBaseType > :: PyClassMutability + as :: pyo3 :: impl_ :: pycell :: PyClassMutability > :: ImmutableChild; + type Dict = ::pyo3::impl_::pyclass::PyClassDummySlot; + type WeakRef = ::pyo3::impl_::pyclass::PyClassDummySlot; + type BaseNativeType = + ::BaseNativeType; + fn items_iter() -> ::pyo3::impl_::pyclass::PyClassItemsIter { + use ::pyo3::impl_::pyclass::*; + let collector = PyClassImplCollector::::new(); + static INTRINSIC_ITEMS: PyClassItems = PyClassItems { + methods: &[ + ::pyo3::class::PyMethodDefType::Getter(::pyo3::class::PyGetterDef::new( + "_0\0", + ::pyo3::impl_::pymethods::PyGetter(TupleEnum_Full::__pymethod_get__0__), + "\0", + )), + ::pyo3::class::PyMethodDefType::Getter(::pyo3::class::PyGetterDef::new( + "_1\0", + ::pyo3::impl_::pymethods::PyGetter(TupleEnum_Full::__pymethod_get__1__), + "\0", + )), + ::pyo3::class::PyMethodDefType::Getter(::pyo3::class::PyGetterDef::new( + "_2\0", + ::pyo3::impl_::pymethods::PyGetter(TupleEnum_Full::__pymethod_get__2__), + "\0", + )), + ::pyo3::class::PyMethodDefType::ClassAttribute({ + ::pyo3::class::PyClassAttributeDef::new( + "__match_args__\0", + ::pyo3::impl_::pymethods::PyClassAttributeFactory( + TupleEnum_Full::__pymethod___match_args____, + ), + ) + }), + ], + slots: &[ + { + unsafe extern "C" fn trampoline( + _slf: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::ffi::Py_ssize_t { + ::pyo3::impl_::trampoline::lenfunc( + _slf, + TupleEnum_Full::__pymethod___len____, + ) + } + ::pyo3::ffi::PyType_Slot { + slot: ::pyo3::ffi::Py_mp_length, + pfunc: trampoline as ::pyo3::ffi::lenfunc as _, + } + }, + { + unsafe extern "C" fn trampoline( + _slf: *mut ::pyo3::ffi::PyObject, + arg0: *mut ::pyo3::ffi::PyObject, + ) -> *mut ::pyo3::ffi::PyObject { + ::pyo3::impl_::trampoline::binaryfunc( + _slf, + arg0, + TupleEnum_Full::__pymethod___getitem____, + ) + } + ::pyo3::ffi::PyType_Slot { + slot: ::pyo3::ffi::Py_mp_subscript, + pfunc: trampoline as ::pyo3::ffi::binaryfunc as _, + } + }, + ::pyo3::ffi::PyType_Slot { + slot: ::pyo3::ffi::Py_tp_new, + pfunc: { + unsafe extern "C" fn trampoline( + subtype: *mut ::pyo3::ffi::PyTypeObject, + args: *mut ::pyo3::ffi::PyObject, + kwargs: *mut ::pyo3::ffi::PyObject, + ) -> *mut ::pyo3::ffi::PyObject { + use ::pyo3::impl_::pyclass::*; + #[allow(unknown_lints, non_local_definitions)] + impl PyClassNewTextSignature for PyClassImplCollector { + #[inline] + fn new_text_signature(self) -> ::std::option::Option<&'static str> { + ::std::option::Option::Some("(_0, _1, _2)") + } + } + ::pyo3::impl_::trampoline::newfunc( + subtype, + args, + kwargs, + TupleEnum_Full::__pymethod___new____, + ) + } + trampoline + } as ::pyo3::ffi::newfunc as _, + }, + ], + }; + PyClassItemsIter::new(&INTRINSIC_ITEMS, collector.py_methods()) + } + fn doc(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<&'static ::std::ffi::CStr> { + use ::pyo3::impl_::pyclass::*; + static DOC: ::pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = + ::pyo3::sync::GILOnceCell::new(); + DOC.get_or_try_init(py, || { + let collector = PyClassImplCollector::::new(); + build_pyclass_doc( + ::NAME, + "\0", + collector.new_text_signature(), + ) + }) + .map(::std::ops::Deref::deref) + } + fn lazy_type_object() -> &'static ::pyo3::impl_::pyclass::LazyTypeObject { + use ::pyo3::impl_::pyclass::LazyTypeObject; + static TYPE_OBJECT: LazyTypeObject = LazyTypeObject::new(); + &TYPE_OBJECT + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +impl TupleEnum_Full { + unsafe fn __pymethod_get__0__( + py: ::pyo3::Python<'_>, + _slf: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { + #[allow(clippy::let_unit_value)] + let result = ::pyo3::callback::convert( + py, + TupleEnum_Full::_0( + ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) + .downcast::() + .map_err(::std::convert::Into::<::pyo3::PyErr>::into) + .and_then( + #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] + |bound| { + ::std::convert::TryFrom::try_from(bound) + .map_err(::std::convert::Into::into) + }, + )?, + ), + ); + result + } + unsafe fn __pymethod_get__1__( + py: ::pyo3::Python<'_>, + _slf: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { + #[allow(clippy::let_unit_value)] + let result = ::pyo3::callback::convert( + py, + TupleEnum_Full::_1( + ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) + .downcast::() + .map_err(::std::convert::Into::<::pyo3::PyErr>::into) + .and_then( + #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] + |bound| { + ::std::convert::TryFrom::try_from(bound) + .map_err(::std::convert::Into::into) + }, + )?, + ), + ); + result + } + unsafe fn __pymethod_get__2__( + py: ::pyo3::Python<'_>, + _slf: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { + #[allow(clippy::let_unit_value)] + let result = ::pyo3::callback::convert( + py, + TupleEnum_Full::_2( + ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) + .downcast::() + .map_err(::std::convert::Into::<::pyo3::PyErr>::into) + .and_then( + #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] + |bound| { + ::std::convert::TryFrom::try_from(bound) + .map_err(::std::convert::Into::into) + }, + )?, + ), + ); + result + } + fn __pymethod___match_args____(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<::pyo3::PyObject> { + let function = TupleEnum_Full::__match_args__; + ::pyo3::impl_::wrap::map_result_into_py( + py, + ::pyo3::impl_::wrap::OkWrap::wrap(function()) + .map_err(::core::convert::Into::<::pyo3::PyErr>::into), + ) + } + unsafe fn __pymethod___len____( + py: ::pyo3::Python<'_>, + _raw_slf: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::PyResult<::pyo3::ffi::Py_ssize_t> { + let function = TupleEnum_Full::__len__; + let _slf = _raw_slf; + #[allow(clippy::let_unit_value)] + let result = TupleEnum_Full::__len__( + ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) + .downcast::() + .map_err(::std::convert::Into::<::pyo3::PyErr>::into) + .and_then( + #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] + |bound| { + ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) + }, + )?, + ); + ::pyo3::callback::convert(py, result) + } + unsafe fn __pymethod___getitem____( + py: ::pyo3::Python<'_>, + _raw_slf: *mut ::pyo3::ffi::PyObject, + arg0: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { + let function = TupleEnum_Full::__getitem__; + let _slf = _raw_slf; + #[allow(clippy::let_unit_value)] + let mut holder_0 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; + let gil_refs_checker_0 = ::pyo3::impl_::deprecations::GilRefs::new(); + let result = TupleEnum_Full::__getitem__( + ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) + .downcast::() + .map_err(::std::convert::Into::<::pyo3::PyErr>::into) + .and_then( + #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] + |bound| { + ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) + }, + )?, + ::pyo3::impl_::deprecations::inspect_type( + ::pyo3::impl_::extract_argument::extract_argument( + ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &arg0).0, + &mut holder_0, + "idx", + )?, + &gil_refs_checker_0, + ), + ); + gil_refs_checker_0.function_arg(); + ::pyo3::callback::convert(py, result) + } + unsafe fn __pymethod___new____( + py: ::pyo3::Python<'_>, + _slf: *mut ::pyo3::ffi::PyTypeObject, + _args: *mut ::pyo3::ffi::PyObject, + _kwargs: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { + use ::pyo3::callback::IntoPyCallbackOutput; + let _slf_ref = &_slf; + let function = TupleEnum_Full::__pymethod_constructor__; + const DESCRIPTION: ::pyo3::impl_::extract_argument::FunctionDescription = + ::pyo3::impl_::extract_argument::FunctionDescription { + cls_name: ::std::option::Option::Some( + ::NAME, + ), + func_name: stringify!(__new__), + positional_parameter_names: &["_0", "_1", "_2"], + positional_only_parameters: 0usize, + required_positional_parameters: 3usize, + keyword_only_parameters: &[], + }; + let mut output = [::std::option::Option::None; 3usize]; + let + (_args, _kwargs) = DESCRIPTION.extract_arguments_tuple_dict :: < :: + pyo3 :: impl_ :: extract_argument :: NoVarargs, :: pyo3 :: impl_ :: + extract_argument :: NoVarkeywords > (py, _args, _kwargs, & mut output) + ? ; + #[allow(clippy::let_unit_value)] + let mut holder_0 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; + let mut holder_1 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; + let mut holder_2 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; + let gil_refs_checker_0 = ::pyo3::impl_::deprecations::GilRefs::new(); + let gil_refs_checker_1 = ::pyo3::impl_::deprecations::GilRefs::new(); + let gil_refs_checker_2 = ::pyo3::impl_::deprecations::GilRefs::new(); + let result = TupleEnum_Full::__pymethod_constructor__( + py, + ::pyo3::impl_::deprecations::inspect_type( + ::pyo3::impl_::extract_argument::extract_argument( + ::pyo3::impl_::extract_argument::unwrap_required_argument( + output[0usize].as_deref(), + ), + &mut holder_0, + "_0", + )?, + &gil_refs_checker_0, + ), + ::pyo3::impl_::deprecations::inspect_type( + ::pyo3::impl_::extract_argument::extract_argument( + ::pyo3::impl_::extract_argument::unwrap_required_argument( + output[1usize].as_deref(), + ), + &mut holder_1, + "_1", + )?, + &gil_refs_checker_1, + ), + ::pyo3::impl_::deprecations::inspect_type( + ::pyo3::impl_::extract_argument::extract_argument( + ::pyo3::impl_::extract_argument::unwrap_required_argument( + output[2usize].as_deref(), + ), + &mut holder_2, + "_2", + )?, + &gil_refs_checker_2, + ), + ); + let initializer: ::pyo3::PyClassInitializer = result.convert(py)?; + gil_refs_checker_0.function_arg(); + gil_refs_checker_1.function_arg(); + gil_refs_checker_2.function_arg(); + ::pyo3::impl_::pymethods::tp_new_impl(py, initializer, _slf) + } +} +impl TupleEnum_Full { + #[doc(hidden)] + pub const _PYO3_DEF: ::pyo3::impl_::pymodule::AddClassToModule = + ::pyo3::impl_::pymodule::AddClassToModule::new(); +} +impl ::pyo3::PyClass for TupleEnum_EmptyTuple { + type Frozen = ::pyo3::pyclass::boolean_struct::True; +} +impl<'a, 'py> ::pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> + for &'a TupleEnum_EmptyTuple +{ + type Holder = ::std::option::Option<::pyo3::PyRef<'py, TupleEnum_EmptyTuple>>; + #[inline] + fn extract( + obj: &'a ::pyo3::Bound<'py, ::pyo3::PyAny>, + holder: &'a mut Self::Holder, + ) -> ::pyo3::PyResult { + ::pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder) + } +} +impl ::pyo3::impl_::pyclass::PyClassImpl for TupleEnum_EmptyTuple { + const IS_BASETYPE: bool = false; + const IS_SUBCLASS: bool = true; + const IS_MAPPING: bool = false; + const IS_SEQUENCE: bool = false; + type BaseType = TupleEnum; + type ThreadChecker = ::pyo3::impl_::pyclass::SendablePyClass; + type PyClassMutability = << + TupleEnum as :: pyo3 :: impl_ :: pyclass :: PyClassBaseType > :: + PyClassMutability as :: pyo3 :: impl_ :: pycell :: PyClassMutability > :: + ImmutableChild; + type Dict = ::pyo3::impl_::pyclass::PyClassDummySlot; + type WeakRef = ::pyo3::impl_::pyclass::PyClassDummySlot; + type BaseNativeType = + ::BaseNativeType; + fn items_iter() -> ::pyo3::impl_::pyclass::PyClassItemsIter { + use ::pyo3::impl_::pyclass::*; + let collector = PyClassImplCollector::::new(); + static INTRINSIC_ITEMS: PyClassItems = PyClassItems { + methods: &[::pyo3::class::PyMethodDefType::ClassAttribute({ + ::pyo3::class::PyClassAttributeDef::new( + "__match_args__\0", + ::pyo3::impl_::pymethods::PyClassAttributeFactory( + TupleEnum_EmptyTuple::__pymethod___match_args____, + ), + ) + })], + slots: &[ + { + unsafe extern "C" fn trampoline( + _slf: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::ffi::Py_ssize_t { + ::pyo3::impl_::trampoline::lenfunc( + _slf, + TupleEnum_EmptyTuple::__pymethod___len____, + ) + } + ::pyo3::ffi::PyType_Slot { + slot: ::pyo3::ffi::Py_mp_length, + pfunc: trampoline as ::pyo3::ffi::lenfunc as _, + } + }, + { + unsafe extern "C" fn trampoline( + _slf: *mut ::pyo3::ffi::PyObject, + arg0: *mut ::pyo3::ffi::PyObject, + ) -> *mut ::pyo3::ffi::PyObject { + ::pyo3::impl_::trampoline::binaryfunc( + _slf, + arg0, + TupleEnum_EmptyTuple::__pymethod___getitem____, + ) + } + ::pyo3::ffi::PyType_Slot { + slot: ::pyo3::ffi::Py_mp_subscript, + pfunc: trampoline as ::pyo3::ffi::binaryfunc as _, + } + }, + ::pyo3::ffi::PyType_Slot { + slot: ::pyo3::ffi::Py_tp_new, + pfunc: { + unsafe extern "C" fn trampoline( + subtype: *mut ::pyo3::ffi::PyTypeObject, + args: *mut ::pyo3::ffi::PyObject, + kwargs: *mut ::pyo3::ffi::PyObject, + ) -> *mut ::pyo3::ffi::PyObject { + use ::pyo3::impl_::pyclass::*; + #[allow(unknown_lints, non_local_definitions)] + impl PyClassNewTextSignature for PyClassImplCollector { + #[inline] + fn new_text_signature(self) -> ::std::option::Option<&'static str> { + ::std::option::Option::Some("()") + } + } + ::pyo3::impl_::trampoline::newfunc( + subtype, + args, + kwargs, + TupleEnum_EmptyTuple::__pymethod___new____, + ) + } + trampoline + } as ::pyo3::ffi::newfunc as _, + }, + ], + }; + PyClassItemsIter::new(&INTRINSIC_ITEMS, collector.py_methods()) + } + fn doc(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<&'static ::std::ffi::CStr> { + use ::pyo3::impl_::pyclass::*; + static DOC: ::pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = + ::pyo3::sync::GILOnceCell::new(); + DOC.get_or_try_init(py, || { + let collector = PyClassImplCollector::::new(); + build_pyclass_doc( + ::NAME, + "\0", + collector.new_text_signature(), + ) + }) + .map(::std::ops::Deref::deref) + } + fn lazy_type_object() -> &'static ::pyo3::impl_::pyclass::LazyTypeObject { + use ::pyo3::impl_::pyclass::LazyTypeObject; + static TYPE_OBJECT: LazyTypeObject = LazyTypeObject::new(); + &TYPE_OBJECT + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +impl TupleEnum_EmptyTuple { + fn __pymethod___match_args____(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<::pyo3::PyObject> { + let function = TupleEnum_EmptyTuple::__match_args__; + ::pyo3::impl_::wrap::map_result_into_py( + py, + ::pyo3::impl_::wrap::OkWrap::wrap(function()) + .map_err(::core::convert::Into::<::pyo3::PyErr>::into), + ) + } + unsafe fn __pymethod___len____( + py: ::pyo3::Python<'_>, + _raw_slf: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::PyResult<::pyo3::ffi::Py_ssize_t> { + let function = TupleEnum_EmptyTuple::__len__; + let _slf = _raw_slf; + #[allow(clippy::let_unit_value)] + let result = TupleEnum_EmptyTuple::__len__( + ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) + .downcast::() + .map_err(::std::convert::Into::<::pyo3::PyErr>::into) + .and_then( + #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] + |bound| { + ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) + }, + )?, + ); + ::pyo3::callback::convert(py, result) + } + unsafe fn __pymethod___getitem____( + py: ::pyo3::Python<'_>, + _raw_slf: *mut ::pyo3::ffi::PyObject, + arg0: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { + let function = TupleEnum_EmptyTuple::__getitem__; + let _slf = _raw_slf; + #[allow(clippy::let_unit_value)] + let mut holder_0 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; + let gil_refs_checker_0 = ::pyo3::impl_::deprecations::GilRefs::new(); + let result = TupleEnum_EmptyTuple::__getitem__( + ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) + .downcast::() + .map_err(::std::convert::Into::<::pyo3::PyErr>::into) + .and_then( + #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] + |bound| { + ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) + }, + )?, + ::pyo3::impl_::deprecations::inspect_type( + ::pyo3::impl_::extract_argument::extract_argument( + ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &arg0).0, + &mut holder_0, + "idx", + )?, + &gil_refs_checker_0, + ), + ); + gil_refs_checker_0.function_arg(); + ::pyo3::callback::convert(py, result) + } + unsafe fn __pymethod___new____( + py: ::pyo3::Python<'_>, + _slf: *mut ::pyo3::ffi::PyTypeObject, + _args: *mut ::pyo3::ffi::PyObject, + _kwargs: *mut ::pyo3::ffi::PyObject, + ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { + use ::pyo3::callback::IntoPyCallbackOutput; + let _slf_ref = &_slf; + let function = TupleEnum_EmptyTuple::__pymethod_constructor__; + const DESCRIPTION: ::pyo3::impl_::extract_argument::FunctionDescription = + ::pyo3::impl_::extract_argument::FunctionDescription { + cls_name: ::std::option::Option::Some( + ::NAME, + ), + func_name: stringify!(__new__), + positional_parameter_names: &[], + positional_only_parameters: 0usize, + required_positional_parameters: 0usize, + keyword_only_parameters: &[], + }; + let mut output = [::std::option::Option::None; 0usize]; + let + (_args, _kwargs) = DESCRIPTION.extract_arguments_tuple_dict :: < :: + pyo3 :: impl_ :: extract_argument :: NoVarargs, :: pyo3 :: impl_ :: + extract_argument :: NoVarkeywords > (py, _args, _kwargs, & mut output) + ? ; + #[allow(clippy::let_unit_value)] + let result = TupleEnum_EmptyTuple::__pymethod_constructor__(py); + let initializer: ::pyo3::PyClassInitializer = result.convert(py)?; + ::pyo3::impl_::pymethods::tp_new_impl(py, initializer, _slf) + } +} +impl TupleEnum_EmptyTuple { + #[doc(hidden)] + pub const _PYO3_DEF: ::pyo3::impl_::pymodule::AddClassToModule = + ::pyo3::impl_::pymodule::AddClassToModule::new(); +} +#[doc(hidden)] +#[allow(non_snake_case)] +impl TupleEnum_Full { + fn __pymethod_constructor__( + py: ::pyo3::Python<'_>, + _0: i32, + _1: f64, + _2: bool, + ) -> ::pyo3::PyClassInitializer { + let base_value = TupleEnum::Full(_0, _1, _2); + ::pyo3::PyClassInitializer::from(base_value).add_subclass(TupleEnum_Full) + } + fn __len__(slf: ::pyo3::PyRef) -> ::pyo3::PyResult { + Ok(3usize) + } + fn __getitem__(slf: ::pyo3::PyRef, idx: usize) -> ::pyo3::PyResult<::pyo3::PyObject> { + let py = slf.py(); + match idx { + 0usize => Ok(::pyo3::IntoPy::into_py(TupleEnum_Full::_0(slf)?, py)), + 1usize => Ok(::pyo3::IntoPy::into_py(TupleEnum_Full::_1(slf)?, py)), + 2usize => Ok(::pyo3::IntoPy::into_py(TupleEnum_Full::_2(slf)?, py)), + _ => Err(pyo3::exceptions::PyIndexError::new_err( + "tuple index out of range", + )), + } + } + fn __match_args__(slf: ::pyo3::PyRef) -> ::pyo3::PyResult<(i32, f64, bool)> { + match &*slf.into_super() { + TupleEnum::Full(_0, _1, _2) => Ok((_0.clone(), _1.clone(), _2.clone())), + _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), + } + } + fn _0(slf: ::pyo3::PyRef) -> ::pyo3::PyResult { + match &*slf.into_super() { + TupleEnum::Full(val, _, _) => Ok(val.clone()), + _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), + } + } + fn _1(slf: ::pyo3::PyRef) -> ::pyo3::PyResult { + match &*slf.into_super() { + TupleEnum::Full(_, val, _) => Ok(val.clone()), + _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), + } + } + fn _2(slf: ::pyo3::PyRef) -> ::pyo3::PyResult { + match &*slf.into_super() { + TupleEnum::Full(_, _, val) => Ok(val.clone()), + _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), + } + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +impl TupleEnum_EmptyTuple { + fn __pymethod_constructor__( + py: ::pyo3::Python<'_>, + ) -> ::pyo3::PyClassInitializer { + let base_value = TupleEnum::EmptyTuple(); + ::pyo3::PyClassInitializer::from(base_value).add_subclass(TupleEnum_EmptyTuple) + } + fn __len__(slf: ::pyo3::PyRef) -> ::pyo3::PyResult { + Ok(0usize) + } + fn __getitem__(slf: ::pyo3::PyRef, idx: usize) -> ::pyo3::PyResult<::pyo3::PyObject> { + let py = slf.py(); + match idx { + _ => Err(pyo3::exceptions::PyIndexError::new_err( + "tuple index out of range", + )), + } + } + fn __match_args__(slf: ::pyo3::PyRef) -> ::pyo3::PyResult<()> { + match &*slf.into_super() { + TupleEnum::EmptyTuple() => Ok(()), + _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), + } + } +} #[pyfunction] pub fn do_tuple_stuff(thing: &TupleEnum) -> TupleEnum { From 91c457f02c3c52076c937559aa0586f46cf601d9 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 10 May 2024 12:26:34 +0200 Subject: [PATCH 17/24] support match_args for tuple enum --- pyo3-macros-backend/src/pyclass.rs | 95 ++-- pytests/src/enums.rs | 812 +---------------------------- pytests/tests/test_enums_match.py | 2 - 3 files changed, 64 insertions(+), 845 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 3b1f156967e..f182139abb2 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -980,21 +980,21 @@ fn impl_complex_enum( } Ok(quote! { - #pytypeinfo + #pytypeinfo - #pyclass_impls + #pyclass_impls - #[doc(hidden)] - #[allow(non_snake_case)] - impl #cls {} + #[doc(hidden)] + #[allow(non_snake_case)] + impl #cls {} - #(#variant_cls_zsts)* + #(#variant_cls_zsts)* - #(#variant_cls_pytypeinfos)* + #(#variant_cls_pytypeinfos)* - #(#variant_cls_pyclass_impls)* + #(#variant_cls_pyclass_impls)* - #(#variant_cls_impls)* + #(#variant_cls_impls)* }) } @@ -1073,8 +1073,8 @@ fn impl_complex_enum_tuple_variant_field_getters( variant_cls_type: &syn::Type, variant_ident: &&Ident, field_names: &mut Vec, - fields_with_types: &mut Vec, -) -> Result<(Vec, Vec)> { + fields_types: &mut Vec, +) -> Result<(Vec, Vec)> { let Ctx { pyo3_path } = ctx; let mut field_getters = vec![]; @@ -1083,7 +1083,6 @@ fn impl_complex_enum_tuple_variant_field_getters( for (index, field) in variant.fields.iter().enumerate() { let field_name = format_ident!("_{}", index); let field_type = field.ty; - let field_with_type = quote! { #field_name : #field_type }; let field_getter = complex_enum_variant_field_getter(&variant_cls_type, &field_name, field.span, ctx)?; @@ -1099,7 +1098,7 @@ fn impl_complex_enum_tuple_variant_field_getters( }) .collect(); - let field_getter_impl = quote! { + let field_getter_impl: syn::ImplItemFn = parse_quote! { fn #field_name(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult<#field_type> { match &*slf.into_super() { #enum_name::#variant_ident ( #(#field_access_tokens), *) => Ok(val.clone()), @@ -1109,7 +1108,7 @@ fn impl_complex_enum_tuple_variant_field_getters( }; field_names.push(field_name); - fields_with_types.push(field_with_type); + fields_types.push(field_type.clone()); field_getters.push(field_getter); field_getter_impls.push(field_getter_impl); } @@ -1125,14 +1124,12 @@ fn impl_complex_enum_tuple_variant_len( ) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> { let Ctx { pyo3_path } = ctx; - let len_method_impl = quote! { + let mut len_method_impl: syn::ImplItemFn = parse_quote! { fn __len__(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult { Ok(#num_fields) } }; - let mut len_method_impl: syn::ImplItemFn = syn::parse2(len_method_impl).unwrap(); - let variant_len = crate::pymethod::impl_py_slot_def(&variant_cls_type, ctx, &mut len_method_impl.sig)?; @@ -1161,27 +1158,54 @@ fn impl_complex_enum_tuple_variant_getitem( }) .collect(); - let matcher = quote! { - let py = slf.py(); - match idx { - #( #match_arms, )* - _ => Err(pyo3::exceptions::PyIndexError::new_err("tuple index out of range")), - } - }; - - let get_item_method_impl = quote! { + let mut get_item_method_impl: syn::ImplItemFn = parse_quote! { fn __getitem__(slf: #pyo3_path::PyRef, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::PyObject> { - #matcher + let py = slf.py(); + match idx { + #( #match_arms, )* + _ => Err(pyo3::exceptions::PyIndexError::new_err("tuple index out of range")), + } } }; - let mut get_item_method_impl: syn::ImplItemFn = syn::parse2(get_item_method_impl).unwrap(); let variant_getitem = crate::pymethod::impl_py_slot_def(&variant_cls_type, ctx, &mut get_item_method_impl.sig)?; Ok((variant_getitem, get_item_method_impl)) } +fn impl_complex_enum_tuple_variant_match_args( + ctx: &Ctx, + variant_cls_type: &syn::Type, + field_names: &mut Vec, +) -> Result<(MethodAndMethodDef, syn::ImplItemConst)> { + let args_tp = field_names.iter().map(|_| { + quote! { &'static str } + }); + + let match_args_const_impl: syn::ImplItemConst = parse_quote! { + const __match_args__: ( #(#args_tp),* ) = ( + #(stringify!(#field_names),)* + ); + }; + + let spec = ConstSpec { + rust_ident: format_ident!("__match_args__"), + attributes: ConstAttributes { + is_class_attr: true, + name: Some(NameAttribute { + kw: syn::parse_quote! { name }, + value: NameLitStr(format_ident!("__match_args__")), + }), + deprecations: Deprecations::new(ctx), + }, + }; + + let variant_match_args = gen_py_const(variant_cls_type, &spec, ctx); + + Ok((variant_match_args, match_args_const_impl)) +} + fn impl_complex_enum_tuple_variant_cls( enum_name: &syn::Ident, variant: &PyClassEnumTupleVariant<'_>, @@ -1196,16 +1220,16 @@ fn impl_complex_enum_tuple_variant_cls( // represents the index of the field let mut field_names: Vec = vec![]; - let mut fields_with_types: Vec = vec![]; + let mut field_types: Vec = vec![]; - let (field_getters, field_getter_impls) = impl_complex_enum_tuple_variant_field_getters( + let (mut field_getters, field_getter_impls) = impl_complex_enum_tuple_variant_field_getters( ctx, variant, enum_name, &variant_cls_type, &variant_ident, &mut field_names, - &mut fields_with_types, + &mut field_types, )?; let num_fields = variant.fields.len(); @@ -1220,11 +1244,16 @@ fn impl_complex_enum_tuple_variant_cls( slots.push(variant_getitem); + let (variant_match_args, match_args_method_impl) = + impl_complex_enum_tuple_variant_match_args(ctx, &variant_cls_type, &mut field_names)?; + + field_getters.push(variant_match_args); + let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] impl #variant_cls { - fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#fields_with_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> { + fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#field_names : #field_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> { let base_value = #enum_name::#variant_ident ( #(#field_names,)* ); #pyo3_path::PyClassInitializer::from(base_value).add_subclass(#variant_cls) } @@ -1233,6 +1262,8 @@ fn impl_complex_enum_tuple_variant_cls( #getitem_method_impl + #match_args_method_impl + #(#field_getter_impls)* } }; diff --git a/pytests/src/enums.rs b/pytests/src/enums.rs index b6e5d41d635..d6566a8931b 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -84,821 +84,11 @@ pub fn do_complex_stuff(thing: &ComplexEnum) -> ComplexEnum { } } +#[pyclass] pub enum TupleEnum { Full(i32, f64, bool), EmptyTuple(), } -#[allow(deprecated)] -unsafe impl ::pyo3::type_object::HasPyGilRef for TupleEnum { - type AsRefTarget = ::pyo3::PyCell; -} -unsafe impl ::pyo3::type_object::PyTypeInfo for TupleEnum { - const NAME: &'static str = "TupleEnum"; - const MODULE: ::std::option::Option<&'static str> = ::core::option::Option::None; - #[inline] - fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { - use ::pyo3::prelude::PyTypeMethods; - ::lazy_type_object() - .get_or_init(py) - .as_type_ptr() - } -} -impl ::pyo3::PyClass for TupleEnum { - type Frozen = ::pyo3::pyclass::boolean_struct::True; -} -impl<'a, 'py> ::pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a TupleEnum { - type Holder = ::std::option::Option<::pyo3::PyRef<'py, TupleEnum>>; - #[inline] - fn extract( - obj: &'a ::pyo3::Bound<'py, ::pyo3::PyAny>, - holder: &'a mut Self::Holder, - ) -> ::pyo3::PyResult { - ::pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder) - } -} -impl ::pyo3::IntoPy<::pyo3::PyObject> for TupleEnum { - fn into_py(self, py: ::pyo3::Python) -> ::pyo3::PyObject { - match self { - TupleEnum::Full { .. } => { - let pyclass_init = - ::pyo3::PyClassInitializer::from(self).add_subclass(TupleEnum_Full); - let variant_value = ::pyo3::Py::new(py, pyclass_init).unwrap(); - ::pyo3::IntoPy::into_py(variant_value, py) - } - TupleEnum::EmptyTuple { .. } => { - let pyclass_init = - ::pyo3::PyClassInitializer::from(self).add_subclass(TupleEnum_EmptyTuple); - let variant_value = ::pyo3::Py::new(py, pyclass_init).unwrap(); - ::pyo3::IntoPy::into_py(variant_value, py) - } - } - } -} -impl ::pyo3::impl_::pyclass::PyClassImpl for TupleEnum { - const IS_BASETYPE: bool = true; - const IS_SUBCLASS: bool = false; - const IS_MAPPING: bool = false; - const IS_SEQUENCE: bool = false; - type BaseType = ::pyo3::PyAny; - type ThreadChecker = ::pyo3::impl_::pyclass::SendablePyClass; - type PyClassMutability = << :: pyo3 :: - PyAny as :: pyo3 :: impl_ :: pyclass :: PyClassBaseType > :: - PyClassMutability as :: pyo3 :: impl_ :: pycell :: PyClassMutability > :: - ImmutableChild; - type Dict = ::pyo3::impl_::pyclass::PyClassDummySlot; - type WeakRef = ::pyo3::impl_::pyclass::PyClassDummySlot; - type BaseNativeType = ::pyo3::PyAny; - fn items_iter() -> ::pyo3::impl_::pyclass::PyClassItemsIter { - use ::pyo3::impl_::pyclass::*; - let collector = PyClassImplCollector::::new(); - static INTRINSIC_ITEMS: PyClassItems = PyClassItems { - methods: &[ - ::pyo3::class::PyMethodDefType::ClassAttribute({ - ::pyo3::class::PyClassAttributeDef::new( - { "Full\0" }, - ::pyo3::impl_::pymethods::PyClassAttributeFactory( - TupleEnum::__pymethod_variant_cls_Full__, - ), - ) - }), - ::pyo3::class::PyMethodDefType::ClassAttribute({ - ::pyo3::class::PyClassAttributeDef::new( - { "EmptyTuple\0" }, - ::pyo3::impl_::pymethods::PyClassAttributeFactory( - TupleEnum::__pymethod_variant_cls_EmptyTuple__, - ), - ) - }), - ], - slots: &[], - }; - PyClassItemsIter::new(&INTRINSIC_ITEMS, collector.py_methods()) - } - fn doc(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<&'static ::std::ffi::CStr> { - use ::pyo3::impl_::pyclass::*; - static DOC: ::pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = - ::pyo3::sync::GILOnceCell::new(); - DOC.get_or_try_init(py, || { - let collector = PyClassImplCollector::::new(); - build_pyclass_doc( - ::NAME, - "\0", - collector.new_text_signature(), - ) - }) - .map(::std::ops::Deref::deref) - } - fn lazy_type_object() -> &'static ::pyo3::impl_::pyclass::LazyTypeObject { - use ::pyo3::impl_::pyclass::LazyTypeObject; - static TYPE_OBJECT: LazyTypeObject = LazyTypeObject::new(); - &TYPE_OBJECT - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -impl TupleEnum { - fn __pymethod_variant_cls_Full__(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<::pyo3::PyObject> { - ::std::result::Result::Ok(py.get_type_bound::().into_any().unbind()) - } - fn __pymethod_variant_cls_EmptyTuple__( - py: ::pyo3::Python<'_>, - ) -> ::pyo3::PyResult<::pyo3::PyObject> { - ::std::result::Result::Ok( - py.get_type_bound::() - .into_any() - .unbind(), - ) - } -} -impl TupleEnum { - #[doc(hidden)] - pub const _PYO3_DEF: ::pyo3::impl_::pymodule::AddClassToModule = - ::pyo3::impl_::pymodule::AddClassToModule::new(); -} -#[doc(hidden)] -#[allow(non_snake_case)] -impl TupleEnum {} -#[doc(hidden)] -#[allow(non_camel_case_types)] -struct TupleEnum_Full; -#[doc(hidden)] -#[allow(non_camel_case_types)] -struct TupleEnum_EmptyTuple; -#[allow(deprecated)] -unsafe impl ::pyo3::type_object::HasPyGilRef for TupleEnum_Full { - type AsRefTarget = ::pyo3::PyCell; -} -unsafe impl ::pyo3::type_object::PyTypeInfo for TupleEnum_Full { - const NAME: &'static str = "TupleEnum_Full"; - const MODULE: ::std::option::Option<&'static str> = ::core::option::Option::None; - #[inline] - fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { - use ::pyo3::prelude::PyTypeMethods; - ::lazy_type_object() - .get_or_init(py) - .as_type_ptr() - } -} -#[allow(deprecated)] -unsafe impl ::pyo3::type_object::HasPyGilRef for TupleEnum_EmptyTuple { - type AsRefTarget = ::pyo3::PyCell; -} -unsafe impl ::pyo3::type_object::PyTypeInfo for TupleEnum_EmptyTuple { - const NAME: &'static str = "TupleEnum_EmptyTuple"; - const MODULE: ::std::option::Option<&'static str> = ::core::option::Option::None; - #[inline] - fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { - use ::pyo3::prelude::PyTypeMethods; - ::lazy_type_object() - .get_or_init(py) - .as_type_ptr() - } -} -impl ::pyo3::PyClass for TupleEnum_Full { - type Frozen = ::pyo3::pyclass::boolean_struct::True; -} -impl<'a, 'py> ::pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a TupleEnum_Full { - type Holder = ::std::option::Option<::pyo3::PyRef<'py, TupleEnum_Full>>; - #[inline] - fn extract( - obj: &'a ::pyo3::Bound<'py, ::pyo3::PyAny>, - holder: &'a mut Self::Holder, - ) -> ::pyo3::PyResult { - ::pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder) - } -} -impl ::pyo3::impl_::pyclass::PyClassImpl for TupleEnum_Full { - const IS_BASETYPE: bool = false; - const IS_SUBCLASS: bool = true; - const IS_MAPPING: bool = false; - const IS_SEQUENCE: bool = false; - type BaseType = TupleEnum; - type ThreadChecker = ::pyo3::impl_::pyclass::SendablePyClass; - type PyClassMutability = << TupleEnum - as :: pyo3 :: impl_ :: pyclass :: PyClassBaseType > :: PyClassMutability - as :: pyo3 :: impl_ :: pycell :: PyClassMutability > :: ImmutableChild; - type Dict = ::pyo3::impl_::pyclass::PyClassDummySlot; - type WeakRef = ::pyo3::impl_::pyclass::PyClassDummySlot; - type BaseNativeType = - ::BaseNativeType; - fn items_iter() -> ::pyo3::impl_::pyclass::PyClassItemsIter { - use ::pyo3::impl_::pyclass::*; - let collector = PyClassImplCollector::::new(); - static INTRINSIC_ITEMS: PyClassItems = PyClassItems { - methods: &[ - ::pyo3::class::PyMethodDefType::Getter(::pyo3::class::PyGetterDef::new( - "_0\0", - ::pyo3::impl_::pymethods::PyGetter(TupleEnum_Full::__pymethod_get__0__), - "\0", - )), - ::pyo3::class::PyMethodDefType::Getter(::pyo3::class::PyGetterDef::new( - "_1\0", - ::pyo3::impl_::pymethods::PyGetter(TupleEnum_Full::__pymethod_get__1__), - "\0", - )), - ::pyo3::class::PyMethodDefType::Getter(::pyo3::class::PyGetterDef::new( - "_2\0", - ::pyo3::impl_::pymethods::PyGetter(TupleEnum_Full::__pymethod_get__2__), - "\0", - )), - ::pyo3::class::PyMethodDefType::ClassAttribute({ - ::pyo3::class::PyClassAttributeDef::new( - "__match_args__\0", - ::pyo3::impl_::pymethods::PyClassAttributeFactory( - TupleEnum_Full::__pymethod___match_args____, - ), - ) - }), - ], - slots: &[ - { - unsafe extern "C" fn trampoline( - _slf: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::ffi::Py_ssize_t { - ::pyo3::impl_::trampoline::lenfunc( - _slf, - TupleEnum_Full::__pymethod___len____, - ) - } - ::pyo3::ffi::PyType_Slot { - slot: ::pyo3::ffi::Py_mp_length, - pfunc: trampoline as ::pyo3::ffi::lenfunc as _, - } - }, - { - unsafe extern "C" fn trampoline( - _slf: *mut ::pyo3::ffi::PyObject, - arg0: *mut ::pyo3::ffi::PyObject, - ) -> *mut ::pyo3::ffi::PyObject { - ::pyo3::impl_::trampoline::binaryfunc( - _slf, - arg0, - TupleEnum_Full::__pymethod___getitem____, - ) - } - ::pyo3::ffi::PyType_Slot { - slot: ::pyo3::ffi::Py_mp_subscript, - pfunc: trampoline as ::pyo3::ffi::binaryfunc as _, - } - }, - ::pyo3::ffi::PyType_Slot { - slot: ::pyo3::ffi::Py_tp_new, - pfunc: { - unsafe extern "C" fn trampoline( - subtype: *mut ::pyo3::ffi::PyTypeObject, - args: *mut ::pyo3::ffi::PyObject, - kwargs: *mut ::pyo3::ffi::PyObject, - ) -> *mut ::pyo3::ffi::PyObject { - use ::pyo3::impl_::pyclass::*; - #[allow(unknown_lints, non_local_definitions)] - impl PyClassNewTextSignature for PyClassImplCollector { - #[inline] - fn new_text_signature(self) -> ::std::option::Option<&'static str> { - ::std::option::Option::Some("(_0, _1, _2)") - } - } - ::pyo3::impl_::trampoline::newfunc( - subtype, - args, - kwargs, - TupleEnum_Full::__pymethod___new____, - ) - } - trampoline - } as ::pyo3::ffi::newfunc as _, - }, - ], - }; - PyClassItemsIter::new(&INTRINSIC_ITEMS, collector.py_methods()) - } - fn doc(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<&'static ::std::ffi::CStr> { - use ::pyo3::impl_::pyclass::*; - static DOC: ::pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = - ::pyo3::sync::GILOnceCell::new(); - DOC.get_or_try_init(py, || { - let collector = PyClassImplCollector::::new(); - build_pyclass_doc( - ::NAME, - "\0", - collector.new_text_signature(), - ) - }) - .map(::std::ops::Deref::deref) - } - fn lazy_type_object() -> &'static ::pyo3::impl_::pyclass::LazyTypeObject { - use ::pyo3::impl_::pyclass::LazyTypeObject; - static TYPE_OBJECT: LazyTypeObject = LazyTypeObject::new(); - &TYPE_OBJECT - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -impl TupleEnum_Full { - unsafe fn __pymethod_get__0__( - py: ::pyo3::Python<'_>, - _slf: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { - #[allow(clippy::let_unit_value)] - let result = ::pyo3::callback::convert( - py, - TupleEnum_Full::_0( - ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) - .downcast::() - .map_err(::std::convert::Into::<::pyo3::PyErr>::into) - .and_then( - #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] - |bound| { - ::std::convert::TryFrom::try_from(bound) - .map_err(::std::convert::Into::into) - }, - )?, - ), - ); - result - } - unsafe fn __pymethod_get__1__( - py: ::pyo3::Python<'_>, - _slf: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { - #[allow(clippy::let_unit_value)] - let result = ::pyo3::callback::convert( - py, - TupleEnum_Full::_1( - ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) - .downcast::() - .map_err(::std::convert::Into::<::pyo3::PyErr>::into) - .and_then( - #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] - |bound| { - ::std::convert::TryFrom::try_from(bound) - .map_err(::std::convert::Into::into) - }, - )?, - ), - ); - result - } - unsafe fn __pymethod_get__2__( - py: ::pyo3::Python<'_>, - _slf: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { - #[allow(clippy::let_unit_value)] - let result = ::pyo3::callback::convert( - py, - TupleEnum_Full::_2( - ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) - .downcast::() - .map_err(::std::convert::Into::<::pyo3::PyErr>::into) - .and_then( - #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] - |bound| { - ::std::convert::TryFrom::try_from(bound) - .map_err(::std::convert::Into::into) - }, - )?, - ), - ); - result - } - fn __pymethod___match_args____(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<::pyo3::PyObject> { - let function = TupleEnum_Full::__match_args__; - ::pyo3::impl_::wrap::map_result_into_py( - py, - ::pyo3::impl_::wrap::OkWrap::wrap(function()) - .map_err(::core::convert::Into::<::pyo3::PyErr>::into), - ) - } - unsafe fn __pymethod___len____( - py: ::pyo3::Python<'_>, - _raw_slf: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::PyResult<::pyo3::ffi::Py_ssize_t> { - let function = TupleEnum_Full::__len__; - let _slf = _raw_slf; - #[allow(clippy::let_unit_value)] - let result = TupleEnum_Full::__len__( - ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) - .downcast::() - .map_err(::std::convert::Into::<::pyo3::PyErr>::into) - .and_then( - #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] - |bound| { - ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) - }, - )?, - ); - ::pyo3::callback::convert(py, result) - } - unsafe fn __pymethod___getitem____( - py: ::pyo3::Python<'_>, - _raw_slf: *mut ::pyo3::ffi::PyObject, - arg0: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { - let function = TupleEnum_Full::__getitem__; - let _slf = _raw_slf; - #[allow(clippy::let_unit_value)] - let mut holder_0 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; - let gil_refs_checker_0 = ::pyo3::impl_::deprecations::GilRefs::new(); - let result = TupleEnum_Full::__getitem__( - ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) - .downcast::() - .map_err(::std::convert::Into::<::pyo3::PyErr>::into) - .and_then( - #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] - |bound| { - ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) - }, - )?, - ::pyo3::impl_::deprecations::inspect_type( - ::pyo3::impl_::extract_argument::extract_argument( - ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &arg0).0, - &mut holder_0, - "idx", - )?, - &gil_refs_checker_0, - ), - ); - gil_refs_checker_0.function_arg(); - ::pyo3::callback::convert(py, result) - } - unsafe fn __pymethod___new____( - py: ::pyo3::Python<'_>, - _slf: *mut ::pyo3::ffi::PyTypeObject, - _args: *mut ::pyo3::ffi::PyObject, - _kwargs: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { - use ::pyo3::callback::IntoPyCallbackOutput; - let _slf_ref = &_slf; - let function = TupleEnum_Full::__pymethod_constructor__; - const DESCRIPTION: ::pyo3::impl_::extract_argument::FunctionDescription = - ::pyo3::impl_::extract_argument::FunctionDescription { - cls_name: ::std::option::Option::Some( - ::NAME, - ), - func_name: stringify!(__new__), - positional_parameter_names: &["_0", "_1", "_2"], - positional_only_parameters: 0usize, - required_positional_parameters: 3usize, - keyword_only_parameters: &[], - }; - let mut output = [::std::option::Option::None; 3usize]; - let - (_args, _kwargs) = DESCRIPTION.extract_arguments_tuple_dict :: < :: - pyo3 :: impl_ :: extract_argument :: NoVarargs, :: pyo3 :: impl_ :: - extract_argument :: NoVarkeywords > (py, _args, _kwargs, & mut output) - ? ; - #[allow(clippy::let_unit_value)] - let mut holder_0 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; - let mut holder_1 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; - let mut holder_2 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; - let gil_refs_checker_0 = ::pyo3::impl_::deprecations::GilRefs::new(); - let gil_refs_checker_1 = ::pyo3::impl_::deprecations::GilRefs::new(); - let gil_refs_checker_2 = ::pyo3::impl_::deprecations::GilRefs::new(); - let result = TupleEnum_Full::__pymethod_constructor__( - py, - ::pyo3::impl_::deprecations::inspect_type( - ::pyo3::impl_::extract_argument::extract_argument( - ::pyo3::impl_::extract_argument::unwrap_required_argument( - output[0usize].as_deref(), - ), - &mut holder_0, - "_0", - )?, - &gil_refs_checker_0, - ), - ::pyo3::impl_::deprecations::inspect_type( - ::pyo3::impl_::extract_argument::extract_argument( - ::pyo3::impl_::extract_argument::unwrap_required_argument( - output[1usize].as_deref(), - ), - &mut holder_1, - "_1", - )?, - &gil_refs_checker_1, - ), - ::pyo3::impl_::deprecations::inspect_type( - ::pyo3::impl_::extract_argument::extract_argument( - ::pyo3::impl_::extract_argument::unwrap_required_argument( - output[2usize].as_deref(), - ), - &mut holder_2, - "_2", - )?, - &gil_refs_checker_2, - ), - ); - let initializer: ::pyo3::PyClassInitializer = result.convert(py)?; - gil_refs_checker_0.function_arg(); - gil_refs_checker_1.function_arg(); - gil_refs_checker_2.function_arg(); - ::pyo3::impl_::pymethods::tp_new_impl(py, initializer, _slf) - } -} -impl TupleEnum_Full { - #[doc(hidden)] - pub const _PYO3_DEF: ::pyo3::impl_::pymodule::AddClassToModule = - ::pyo3::impl_::pymodule::AddClassToModule::new(); -} -impl ::pyo3::PyClass for TupleEnum_EmptyTuple { - type Frozen = ::pyo3::pyclass::boolean_struct::True; -} -impl<'a, 'py> ::pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> - for &'a TupleEnum_EmptyTuple -{ - type Holder = ::std::option::Option<::pyo3::PyRef<'py, TupleEnum_EmptyTuple>>; - #[inline] - fn extract( - obj: &'a ::pyo3::Bound<'py, ::pyo3::PyAny>, - holder: &'a mut Self::Holder, - ) -> ::pyo3::PyResult { - ::pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder) - } -} -impl ::pyo3::impl_::pyclass::PyClassImpl for TupleEnum_EmptyTuple { - const IS_BASETYPE: bool = false; - const IS_SUBCLASS: bool = true; - const IS_MAPPING: bool = false; - const IS_SEQUENCE: bool = false; - type BaseType = TupleEnum; - type ThreadChecker = ::pyo3::impl_::pyclass::SendablePyClass; - type PyClassMutability = << - TupleEnum as :: pyo3 :: impl_ :: pyclass :: PyClassBaseType > :: - PyClassMutability as :: pyo3 :: impl_ :: pycell :: PyClassMutability > :: - ImmutableChild; - type Dict = ::pyo3::impl_::pyclass::PyClassDummySlot; - type WeakRef = ::pyo3::impl_::pyclass::PyClassDummySlot; - type BaseNativeType = - ::BaseNativeType; - fn items_iter() -> ::pyo3::impl_::pyclass::PyClassItemsIter { - use ::pyo3::impl_::pyclass::*; - let collector = PyClassImplCollector::::new(); - static INTRINSIC_ITEMS: PyClassItems = PyClassItems { - methods: &[::pyo3::class::PyMethodDefType::ClassAttribute({ - ::pyo3::class::PyClassAttributeDef::new( - "__match_args__\0", - ::pyo3::impl_::pymethods::PyClassAttributeFactory( - TupleEnum_EmptyTuple::__pymethod___match_args____, - ), - ) - })], - slots: &[ - { - unsafe extern "C" fn trampoline( - _slf: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::ffi::Py_ssize_t { - ::pyo3::impl_::trampoline::lenfunc( - _slf, - TupleEnum_EmptyTuple::__pymethod___len____, - ) - } - ::pyo3::ffi::PyType_Slot { - slot: ::pyo3::ffi::Py_mp_length, - pfunc: trampoline as ::pyo3::ffi::lenfunc as _, - } - }, - { - unsafe extern "C" fn trampoline( - _slf: *mut ::pyo3::ffi::PyObject, - arg0: *mut ::pyo3::ffi::PyObject, - ) -> *mut ::pyo3::ffi::PyObject { - ::pyo3::impl_::trampoline::binaryfunc( - _slf, - arg0, - TupleEnum_EmptyTuple::__pymethod___getitem____, - ) - } - ::pyo3::ffi::PyType_Slot { - slot: ::pyo3::ffi::Py_mp_subscript, - pfunc: trampoline as ::pyo3::ffi::binaryfunc as _, - } - }, - ::pyo3::ffi::PyType_Slot { - slot: ::pyo3::ffi::Py_tp_new, - pfunc: { - unsafe extern "C" fn trampoline( - subtype: *mut ::pyo3::ffi::PyTypeObject, - args: *mut ::pyo3::ffi::PyObject, - kwargs: *mut ::pyo3::ffi::PyObject, - ) -> *mut ::pyo3::ffi::PyObject { - use ::pyo3::impl_::pyclass::*; - #[allow(unknown_lints, non_local_definitions)] - impl PyClassNewTextSignature for PyClassImplCollector { - #[inline] - fn new_text_signature(self) -> ::std::option::Option<&'static str> { - ::std::option::Option::Some("()") - } - } - ::pyo3::impl_::trampoline::newfunc( - subtype, - args, - kwargs, - TupleEnum_EmptyTuple::__pymethod___new____, - ) - } - trampoline - } as ::pyo3::ffi::newfunc as _, - }, - ], - }; - PyClassItemsIter::new(&INTRINSIC_ITEMS, collector.py_methods()) - } - fn doc(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<&'static ::std::ffi::CStr> { - use ::pyo3::impl_::pyclass::*; - static DOC: ::pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = - ::pyo3::sync::GILOnceCell::new(); - DOC.get_or_try_init(py, || { - let collector = PyClassImplCollector::::new(); - build_pyclass_doc( - ::NAME, - "\0", - collector.new_text_signature(), - ) - }) - .map(::std::ops::Deref::deref) - } - fn lazy_type_object() -> &'static ::pyo3::impl_::pyclass::LazyTypeObject { - use ::pyo3::impl_::pyclass::LazyTypeObject; - static TYPE_OBJECT: LazyTypeObject = LazyTypeObject::new(); - &TYPE_OBJECT - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -impl TupleEnum_EmptyTuple { - fn __pymethod___match_args____(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<::pyo3::PyObject> { - let function = TupleEnum_EmptyTuple::__match_args__; - ::pyo3::impl_::wrap::map_result_into_py( - py, - ::pyo3::impl_::wrap::OkWrap::wrap(function()) - .map_err(::core::convert::Into::<::pyo3::PyErr>::into), - ) - } - unsafe fn __pymethod___len____( - py: ::pyo3::Python<'_>, - _raw_slf: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::PyResult<::pyo3::ffi::Py_ssize_t> { - let function = TupleEnum_EmptyTuple::__len__; - let _slf = _raw_slf; - #[allow(clippy::let_unit_value)] - let result = TupleEnum_EmptyTuple::__len__( - ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) - .downcast::() - .map_err(::std::convert::Into::<::pyo3::PyErr>::into) - .and_then( - #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] - |bound| { - ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) - }, - )?, - ); - ::pyo3::callback::convert(py, result) - } - unsafe fn __pymethod___getitem____( - py: ::pyo3::Python<'_>, - _raw_slf: *mut ::pyo3::ffi::PyObject, - arg0: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { - let function = TupleEnum_EmptyTuple::__getitem__; - let _slf = _raw_slf; - #[allow(clippy::let_unit_value)] - let mut holder_0 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; - let gil_refs_checker_0 = ::pyo3::impl_::deprecations::GilRefs::new(); - let result = TupleEnum_EmptyTuple::__getitem__( - ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf) - .downcast::() - .map_err(::std::convert::Into::<::pyo3::PyErr>::into) - .and_then( - #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] - |bound| { - ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) - }, - )?, - ::pyo3::impl_::deprecations::inspect_type( - ::pyo3::impl_::extract_argument::extract_argument( - ::pyo3::impl_::pymethods::BoundRef::ref_from_ptr(py, &arg0).0, - &mut holder_0, - "idx", - )?, - &gil_refs_checker_0, - ), - ); - gil_refs_checker_0.function_arg(); - ::pyo3::callback::convert(py, result) - } - unsafe fn __pymethod___new____( - py: ::pyo3::Python<'_>, - _slf: *mut ::pyo3::ffi::PyTypeObject, - _args: *mut ::pyo3::ffi::PyObject, - _kwargs: *mut ::pyo3::ffi::PyObject, - ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { - use ::pyo3::callback::IntoPyCallbackOutput; - let _slf_ref = &_slf; - let function = TupleEnum_EmptyTuple::__pymethod_constructor__; - const DESCRIPTION: ::pyo3::impl_::extract_argument::FunctionDescription = - ::pyo3::impl_::extract_argument::FunctionDescription { - cls_name: ::std::option::Option::Some( - ::NAME, - ), - func_name: stringify!(__new__), - positional_parameter_names: &[], - positional_only_parameters: 0usize, - required_positional_parameters: 0usize, - keyword_only_parameters: &[], - }; - let mut output = [::std::option::Option::None; 0usize]; - let - (_args, _kwargs) = DESCRIPTION.extract_arguments_tuple_dict :: < :: - pyo3 :: impl_ :: extract_argument :: NoVarargs, :: pyo3 :: impl_ :: - extract_argument :: NoVarkeywords > (py, _args, _kwargs, & mut output) - ? ; - #[allow(clippy::let_unit_value)] - let result = TupleEnum_EmptyTuple::__pymethod_constructor__(py); - let initializer: ::pyo3::PyClassInitializer = result.convert(py)?; - ::pyo3::impl_::pymethods::tp_new_impl(py, initializer, _slf) - } -} -impl TupleEnum_EmptyTuple { - #[doc(hidden)] - pub const _PYO3_DEF: ::pyo3::impl_::pymodule::AddClassToModule = - ::pyo3::impl_::pymodule::AddClassToModule::new(); -} -#[doc(hidden)] -#[allow(non_snake_case)] -impl TupleEnum_Full { - fn __pymethod_constructor__( - py: ::pyo3::Python<'_>, - _0: i32, - _1: f64, - _2: bool, - ) -> ::pyo3::PyClassInitializer { - let base_value = TupleEnum::Full(_0, _1, _2); - ::pyo3::PyClassInitializer::from(base_value).add_subclass(TupleEnum_Full) - } - fn __len__(slf: ::pyo3::PyRef) -> ::pyo3::PyResult { - Ok(3usize) - } - fn __getitem__(slf: ::pyo3::PyRef, idx: usize) -> ::pyo3::PyResult<::pyo3::PyObject> { - let py = slf.py(); - match idx { - 0usize => Ok(::pyo3::IntoPy::into_py(TupleEnum_Full::_0(slf)?, py)), - 1usize => Ok(::pyo3::IntoPy::into_py(TupleEnum_Full::_1(slf)?, py)), - 2usize => Ok(::pyo3::IntoPy::into_py(TupleEnum_Full::_2(slf)?, py)), - _ => Err(pyo3::exceptions::PyIndexError::new_err( - "tuple index out of range", - )), - } - } - fn __match_args__(slf: ::pyo3::PyRef) -> ::pyo3::PyResult<(i32, f64, bool)> { - match &*slf.into_super() { - TupleEnum::Full(_0, _1, _2) => Ok((_0.clone(), _1.clone(), _2.clone())), - _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), - } - } - fn _0(slf: ::pyo3::PyRef) -> ::pyo3::PyResult { - match &*slf.into_super() { - TupleEnum::Full(val, _, _) => Ok(val.clone()), - _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), - } - } - fn _1(slf: ::pyo3::PyRef) -> ::pyo3::PyResult { - match &*slf.into_super() { - TupleEnum::Full(_, val, _) => Ok(val.clone()), - _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), - } - } - fn _2(slf: ::pyo3::PyRef) -> ::pyo3::PyResult { - match &*slf.into_super() { - TupleEnum::Full(_, _, val) => Ok(val.clone()), - _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), - } - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -impl TupleEnum_EmptyTuple { - fn __pymethod_constructor__( - py: ::pyo3::Python<'_>, - ) -> ::pyo3::PyClassInitializer { - let base_value = TupleEnum::EmptyTuple(); - ::pyo3::PyClassInitializer::from(base_value).add_subclass(TupleEnum_EmptyTuple) - } - fn __len__(slf: ::pyo3::PyRef) -> ::pyo3::PyResult { - Ok(0usize) - } - fn __getitem__(slf: ::pyo3::PyRef, idx: usize) -> ::pyo3::PyResult<::pyo3::PyObject> { - let py = slf.py(); - match idx { - _ => Err(pyo3::exceptions::PyIndexError::new_err( - "tuple index out of range", - )), - } - } - fn __match_args__(slf: ::pyo3::PyRef) -> ::pyo3::PyResult<()> { - match &*slf.into_super() { - TupleEnum::EmptyTuple() => Ok(()), - _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), - } - } -} #[pyfunction] pub fn do_tuple_stuff(thing: &TupleEnum) -> TupleEnum { diff --git a/pytests/tests/test_enums_match.py b/pytests/tests/test_enums_match.py index 01c5a4352ed..211d6fe094c 100644 --- a/pytests/tests/test_enums_match.py +++ b/pytests/tests/test_enums_match.py @@ -102,7 +102,6 @@ def test_tuple_enum_match_statement(variant: enums.TupleEnum): enums.TupleEnum.Full(42, 3.14, True), ], ) -@pytest.mark.skip(reason="__match_args__ is not supported for tuple enums yet.") def test_tuple_enum_match_match_args(variant: enums.TupleEnum): match variant: case enums.TupleEnum.Full(x, y, z): @@ -120,7 +119,6 @@ def test_tuple_enum_match_match_args(variant: enums.TupleEnum): enums.TupleEnum.Full(42, 3.14, True), ], ) -@pytest.mark.skip(reason="__match_args__ is not supported for tuple enums yet.") def test_tuple_enum_partial_match(variant: enums.TupleEnum): match variant: case enums.TupleEnum.Full(a): From 9ae0a48f815553c1c7b099c257201b4ba5ff7f00 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 10 May 2024 12:53:36 +0200 Subject: [PATCH 18/24] integrate FnArg now takes Cow --- pyo3-macros-backend/src/pyclass.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index f182139abb2..0cf7e65ab30 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -560,7 +560,6 @@ impl<'a> PyClassComplexEnum<'a> { } } -#[derive(Debug)] enum PyClassEnumVariant<'a> { // TODO(mkovaxx): Unit(PyClassEnumUnitVariant<'a>), Struct(PyClassEnumStructVariant<'a>), @@ -621,35 +620,31 @@ impl<'a> EnumVariant for PyClassEnumUnitVariant<'a> { } /// A struct variant has named fields -#[derive(Debug)] struct PyClassEnumStructVariant<'a> { ident: &'a syn::Ident, fields: Vec>, options: EnumVariantPyO3Options, } -#[derive(Debug)] struct PyClassEnumTupleVariant<'a> { ident: &'a syn::Ident, fields: Vec>, options: EnumVariantPyO3Options, } -#[derive(Debug)] struct PyClassEnumVariantNamedField<'a> { ident: &'a syn::Ident, ty: &'a syn::Type, span: Span, } -#[derive(Debug)] struct PyClassEnumVariantUnnamedField<'a> { ty: &'a syn::Type, span: Span, } /// `#[pyo3()]` options for pyclass enum variants -#[derive(Default, Debug)] +#[derive(Default)] struct EnumVariantPyO3Options { name: Option, constructor: Option, @@ -958,13 +953,11 @@ fn impl_complex_enum( let variant_cls_pytypeinfo = impl_pytypeinfo(&variant_cls, &variant_args, None, ctx); variant_cls_pytypeinfos.push(variant_cls_pytypeinfo); - - let variant_new = complex_enum_variant_new(cls, variant, ctx)?; - let (variant_cls_impl, field_getters, mut slots) = - impl_complex_enum_variant_cls(cls, variant, ctx)?; + impl_complex_enum_variant_cls(cls, &variant, ctx)?; variant_cls_impls.push(variant_cls_impl); + let variant_new = complex_enum_variant_new(cls, variant, ctx)?; slots.push(variant_new); let pyclass_impl = PyClassImplsBuilder::new( @@ -1000,7 +993,7 @@ fn impl_complex_enum( fn impl_complex_enum_variant_cls( enum_name: &syn::Ident, - variant: PyClassEnumVariant<'_>, + variant: &PyClassEnumVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec, Vec)> { match variant { @@ -1015,7 +1008,7 @@ fn impl_complex_enum_variant_cls( fn impl_complex_enum_struct_variant_cls( enum_name: &syn::Ident, - variant: PyClassEnumStructVariant<'_>, + variant: &PyClassEnumStructVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec, Vec)> { let Ctx { pyo3_path } = ctx; @@ -1224,7 +1217,7 @@ fn impl_complex_enum_tuple_variant_cls( let (mut field_getters, field_getter_impls) = impl_complex_enum_tuple_variant_field_getters( ctx, - variant, + &variant, enum_name, &variant_cls_type, &variant_ident, @@ -1453,7 +1446,7 @@ fn complex_enum_struct_variant_new<'a>( } fn complex_enum_tuple_variant_new<'a>( - cls : &'a syn::Ident, + cls: &'a syn::Ident, variant: PyClassEnumTupleVariant<'a>, ctx: &Ctx, ) -> Result { @@ -1476,13 +1469,8 @@ fn complex_enum_tuple_variant_new<'a>( })]; for (i, field) in variant.fields.iter().enumerate() { - // TODO : Tracking issue for Cow in FnArg : #4156 - // ! Warning : This leaks memory. This is a temporary solution until we can modify FnArg to take a Cow - let field_ident = format_ident!("_{}", i); - let boxed_ident = Box::from(field_ident); - let leaky_ident = Box::leak(boxed_ident); args.push(FnArg::Regular(RegularArg { - name: leaky_ident, + name: std::borrow::Cow::Owned(format_ident!("_{}", i)), ty: field.ty, from_py_with: None, default_value: None, From 73b7a8f0f3b2c61298d1ce39ca1996c438583377 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 10 May 2024 14:04:52 +0200 Subject: [PATCH 19/24] fix empty and single element tuples --- pyo3-macros-backend/src/pyclass.rs | 30 ++++++++++++++++++++-------- pytests/src/enums.rs | 7 +++++++ pytests/tests/test_enums_match.py | 17 ++++++++++++++++ tests/ui/invalid_pyclass_enum.stderr | 4 ++-- 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 0cf7e65ab30..9f071db65c6 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1172,14 +1172,28 @@ fn impl_complex_enum_tuple_variant_match_args( variant_cls_type: &syn::Type, field_names: &mut Vec, ) -> Result<(MethodAndMethodDef, syn::ImplItemConst)> { - let args_tp = field_names.iter().map(|_| { - quote! { &'static str } - }); - - let match_args_const_impl: syn::ImplItemConst = parse_quote! { - const __match_args__: ( #(#args_tp),* ) = ( - #(stringify!(#field_names),)* - ); + let match_args_const_impl: syn::ImplItemConst = match field_names.len() { + // This covers the case where the tuple variant has no fields (valid Rust) + 0 => parse_quote! { + const __match_args__: () = (); + }, + 1 => { + let ident = &field_names[0]; + // We need the trailing comma to make it a tuple + parse_quote! { + const __match_args__: (&'static str ,) = (stringify!(#ident) , ); + } + } + _ => { + let args_tp = field_names.iter().map(|_| { + quote! { &'static str } + }); + parse_quote! { + const __match_args__: ( #(#args_tp),* ) = ( + #(stringify!(#field_names),)* + ); + } + } }; let spec = ConstSpec { diff --git a/pytests/src/enums.rs b/pytests/src/enums.rs index d6566a8931b..3fa810b9042 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -8,6 +8,7 @@ use pyo3::{ pub fn enums(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_wrapped(wrap_pyfunction_bound!(do_simple_stuff))?; @@ -84,6 +85,12 @@ pub fn do_complex_stuff(thing: &ComplexEnum) -> ComplexEnum { } } +#[pyclass] +enum SimpleTupleEnum { + Int(i32), + Str(String), +} + #[pyclass] pub enum TupleEnum { Full(i32, f64, bool), diff --git a/pytests/tests/test_enums_match.py b/pytests/tests/test_enums_match.py index 211d6fe094c..bf721026308 100644 --- a/pytests/tests/test_enums_match.py +++ b/pytests/tests/test_enums_match.py @@ -96,6 +96,23 @@ def test_tuple_enum_match_statement(variant: enums.TupleEnum): assert False +@pytest.mark.parametrize( + "variant", + [ + enums.SimpleTupleEnum.Int(42), + enums.SimpleTupleEnum.Str("hello"), + ], +) +def test_simple_tuple_enum_match_statement(variant: enums.SimpleTupleEnum): + match variant: + case enums.SimpleTupleEnum.Int(x): + assert x == 42 + case enums.SimpleTupleEnum.Str(x): + assert x == "hello" + case _: + assert False + + @pytest.mark.parametrize( "variant", [ diff --git a/tests/ui/invalid_pyclass_enum.stderr b/tests/ui/invalid_pyclass_enum.stderr index d25fcaeb6cb..a103d6d41db 100644 --- a/tests/ui/invalid_pyclass_enum.stderr +++ b/tests/ui/invalid_pyclass_enum.stderr @@ -25,7 +25,7 @@ error: Unit variant `UnitVariant` is not yet supported in a complex enum | ^^^^^^^^^^^ error: `constructor` can't be used on a simple enum variant - --> tests/ui/invalid_pyclass_enum.rs:32:12 + --> tests/ui/invalid_pyclass_enum.rs:26:12 | -32 | #[pyo3(constructor = (a, b))] +26 | #[pyo3(constructor = (a, b))] | ^^^^^^^^^^^ From 5bd5690f32f3f8fe719723c5ffb557f0e3db8361 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 10 May 2024 14:27:44 +0200 Subject: [PATCH 20/24] use impl_py_slot_def for cimplex tuple enum slots --- pyo3-macros-backend/src/pyclass.rs | 24 +++++++++++--------- pyo3-macros-backend/src/pymethod.rs | 35 +++-------------------------- 2 files changed, 17 insertions(+), 42 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 9f071db65c6..0e5e460a25b 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -12,7 +12,7 @@ use crate::pyfunction::ConstructorAttribute; use crate::pyimpl::{gen_py_const, PyClassMethodsType}; use crate::pymethod::{ impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType, - SlotDef, __INT__, __REPR__, __RICHCMP__, + SlotDef, __GETITEM__, __INT__, __LEN__, __REPR__, __RICHCMP__, }; use crate::utils::Ctx; use crate::utils::{self, apply_renaming_rule, PythonDoc}; @@ -1078,7 +1078,7 @@ fn impl_complex_enum_tuple_variant_field_getters( let field_type = field.ty; let field_getter = - complex_enum_variant_field_getter(&variant_cls_type, &field_name, field.span, ctx)?; + complex_enum_variant_field_getter(variant_cls_type, &field_name, field.span, ctx)?; // Generate the match arms needed to destructure the tuple and access the specific field let field_access_tokens: Vec<_> = (0..variant.fields.len()) @@ -1124,7 +1124,7 @@ fn impl_complex_enum_tuple_variant_len( }; let variant_len = - crate::pymethod::impl_py_slot_def(&variant_cls_type, ctx, &mut len_method_impl.sig)?; + generate_default_protocol_slot(variant_cls_type, &mut len_method_impl, &__LEN__, ctx)?; Ok((variant_len, len_method_impl)) } @@ -1161,8 +1161,12 @@ fn impl_complex_enum_tuple_variant_getitem( } }; - let variant_getitem = - crate::pymethod::impl_py_slot_def(&variant_cls_type, ctx, &mut get_item_method_impl.sig)?; + let variant_getitem = generate_default_protocol_slot( + variant_cls_type, + &mut get_item_method_impl, + &__GETITEM__, + ctx, + )?; Ok((variant_getitem, get_item_method_impl)) } @@ -1171,7 +1175,7 @@ fn impl_complex_enum_tuple_variant_match_args( ctx: &Ctx, variant_cls_type: &syn::Type, field_names: &mut Vec, -) -> Result<(MethodAndMethodDef, syn::ImplItemConst)> { +) -> (MethodAndMethodDef, syn::ImplItemConst) { let match_args_const_impl: syn::ImplItemConst = match field_names.len() { // This covers the case where the tuple variant has no fields (valid Rust) 0 => parse_quote! { @@ -1210,7 +1214,7 @@ fn impl_complex_enum_tuple_variant_match_args( let variant_match_args = gen_py_const(variant_cls_type, &spec, ctx); - Ok((variant_match_args, match_args_const_impl)) + (variant_match_args, match_args_const_impl) } fn impl_complex_enum_tuple_variant_cls( @@ -1231,10 +1235,10 @@ fn impl_complex_enum_tuple_variant_cls( let (mut field_getters, field_getter_impls) = impl_complex_enum_tuple_variant_field_getters( ctx, - &variant, + variant, enum_name, &variant_cls_type, - &variant_ident, + variant_ident, &mut field_names, &mut field_types, )?; @@ -1252,7 +1256,7 @@ fn impl_complex_enum_tuple_variant_cls( slots.push(variant_getitem); let (variant_match_args, match_args_method_impl) = - impl_complex_enum_tuple_variant_match_args(ctx, &variant_cls_type, &mut field_names)?; + impl_complex_enum_tuple_variant_match_args(ctx, &variant_cls_type, &mut field_names); field_getters.push(variant_match_args); diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 32180d11c4a..f5b11af3c27 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -739,36 +739,6 @@ fn impl_call_getter( Ok(fncall) } -pub fn impl_py_slot_def( - cls: &syn::Type, - ctx: &Ctx, - signature: &mut syn::Signature, -) -> Result { - let spec = FnSpec::parse( - signature, - &mut Vec::new(), - PyFunctionOptions::default(), - ctx, - )?; - - let method_name = spec.python_name.to_string(); - - let slot = match PyMethodKind::from_name(&method_name) { - PyMethodKind::Proto(proto_kind) => { - ensure_no_forbidden_protocol_attributes(&proto_kind, &spec, &method_name)?; - match proto_kind { - PyMethodProtoKind::Slot(slot_def) => slot_def, - _ => { - bail_spanned!(signature.span() => "Only slot methods are supported in #[pyslot]") - } - } - } - _ => bail_spanned!(signature.span() => "Only slot methods are supported in #[pyslot]"), - }; - - Ok(slot.generate_type_slot(&cls, &spec, &method_name, ctx)?) -} - // Used here for PropertyType::Function, used in pyclass for descriptors. pub fn impl_py_getter_def( cls: &syn::Type, @@ -964,7 +934,7 @@ const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_speci ), TokenGenerator(|_| quote! { async_iter_tag }), ); -const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT); +pub const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT); const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc") .arguments(&[Ty::Object]) .ret_ty(Ty::Int); @@ -974,7 +944,8 @@ const __INPLACE_CONCAT__: SlotDef = SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]); const __INPLACE_REPEAT__: SlotDef = SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]); -const __GETITEM__: SlotDef = SlotDef::new("Py_mp_subscript", "binaryfunc").arguments(&[Ty::Object]); +pub const __GETITEM__: SlotDef = + SlotDef::new("Py_mp_subscript", "binaryfunc").arguments(&[Ty::Object]); const __POS__: SlotDef = SlotDef::new("Py_nb_positive", "unaryfunc"); const __NEG__: SlotDef = SlotDef::new("Py_nb_negative", "unaryfunc"); From 78d5078102bae7d269b8dbf2e7096cf0c36bade1 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 10 May 2024 14:30:26 +0200 Subject: [PATCH 21/24] reverse erroneous doc change --- guide/src/class.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/class.md b/guide/src/class.md index 11650b4ecef..e57a8f4b339 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -61,7 +61,7 @@ enum HttpResponse { #[pyclass] enum Shape { Circle { radius: f64 }, - Rectangle { height: f64, width: f64 }, + Rectangle { width: f64, height: f64 }, RegularPolygon(u32, f64), Nothing(), } From 0761854d7b65158a3542ee0ded0095670ff18a83 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Sat, 11 May 2024 14:20:22 +0200 Subject: [PATCH 22/24] Address latest comments --- guide/src/class.md | 6 +- pyo3-macros-backend/src/pyclass.rs | 116 ++++++++++++++--------------- pytests/src/enums.rs | 3 + pytests/tests/test_enums.py | 8 ++ pytests/tests/test_enums_match.py | 3 - 5 files changed, 70 insertions(+), 66 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index e57a8f4b339..d287a20183c 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -1209,8 +1209,8 @@ Python::with_gil(|py| { assert isinstance(square, cls) assert isinstance(square, cls.RegularPolygon) - assert square._0 == 4 - assert square._1 == 10.0 + assert square[0] == 4 # Gets _0 field + assert square[1] == 10.0 # Gets _1 field def count_vertices(cls, shape): match shape: @@ -1218,7 +1218,7 @@ Python::with_gil(|py| { return 0 case cls.Rectangle(): return 4 - case cls.RegularPolygon(_0=n): + case cls.RegularPolygon(n): return n case cls.Nothing(): return 0 diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 0e5e460a25b..9d80b78d42d 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -975,19 +975,19 @@ fn impl_complex_enum( Ok(quote! { #pytypeinfo - #pyclass_impls + #pyclass_impls - #[doc(hidden)] - #[allow(non_snake_case)] - impl #cls {} + #[doc(hidden)] + #[allow(non_snake_case)] + impl #cls {} - #(#variant_cls_zsts)* + #(#variant_cls_zsts)* - #(#variant_cls_pytypeinfos)* + #(#variant_cls_pytypeinfos)* - #(#variant_cls_pyclass_impls)* + #(#variant_cls_pyclass_impls)* - #(#variant_cls_impls)* + #(#variant_cls_impls)* }) } @@ -1006,6 +1006,36 @@ fn impl_complex_enum_variant_cls( } } +fn impl_complex_enum_variant_match_args( + ctx: &Ctx, + variant_cls_type: &syn::Type, + field_names: &mut Vec, +) -> (MethodAndMethodDef, syn::ImplItemConst) { + let match_args_const_impl: syn::ImplItemConst = { + let args_tp = field_names.iter().map(|_| { + quote! { &'static str } + }); + parse_quote! { + const __match_args__: ( #(#args_tp,)* ) = ( + #(stringify!(#field_names),)* + ); + } + }; + + let spec = ConstSpec { + rust_ident: format_ident!("__match_args__"), + attributes: ConstAttributes { + is_class_attr: true, + name: None, + deprecations: Deprecations::new(ctx), + }, + }; + + let variant_match_args = gen_py_const(variant_cls_type, &spec, ctx); + + (variant_match_args, match_args_const_impl) +} + fn impl_complex_enum_struct_variant_cls( enum_name: &syn::Ident, variant: &PyClassEnumStructVariant<'_>, @@ -1043,6 +1073,11 @@ fn impl_complex_enum_struct_variant_cls( field_getter_impls.push(field_getter_impl); } + let (variant_match_args, match_args_const_impl) = + impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names); + + field_getters.push(variant_match_args); + let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] @@ -1052,6 +1087,8 @@ fn impl_complex_enum_struct_variant_cls( #pyo3_path::PyClassInitializer::from(base_value).add_subclass(#variant_cls) } + #match_args_const_impl + #(#field_getter_impls)* } }; @@ -1171,52 +1208,6 @@ fn impl_complex_enum_tuple_variant_getitem( Ok((variant_getitem, get_item_method_impl)) } -fn impl_complex_enum_tuple_variant_match_args( - ctx: &Ctx, - variant_cls_type: &syn::Type, - field_names: &mut Vec, -) -> (MethodAndMethodDef, syn::ImplItemConst) { - let match_args_const_impl: syn::ImplItemConst = match field_names.len() { - // This covers the case where the tuple variant has no fields (valid Rust) - 0 => parse_quote! { - const __match_args__: () = (); - }, - 1 => { - let ident = &field_names[0]; - // We need the trailing comma to make it a tuple - parse_quote! { - const __match_args__: (&'static str ,) = (stringify!(#ident) , ); - } - } - _ => { - let args_tp = field_names.iter().map(|_| { - quote! { &'static str } - }); - parse_quote! { - const __match_args__: ( #(#args_tp),* ) = ( - #(stringify!(#field_names),)* - ); - } - } - }; - - let spec = ConstSpec { - rust_ident: format_ident!("__match_args__"), - attributes: ConstAttributes { - is_class_attr: true, - name: Some(NameAttribute { - kw: syn::parse_quote! { name }, - value: NameLitStr(format_ident!("__match_args__")), - }), - deprecations: Deprecations::new(ctx), - }, - }; - - let variant_match_args = gen_py_const(variant_cls_type, &spec, ctx); - - (variant_match_args, match_args_const_impl) -} - fn impl_complex_enum_tuple_variant_cls( enum_name: &syn::Ident, variant: &PyClassEnumTupleVariant<'_>, @@ -1256,7 +1247,7 @@ fn impl_complex_enum_tuple_variant_cls( slots.push(variant_getitem); let (variant_match_args, match_args_method_impl) = - impl_complex_enum_tuple_variant_match_args(ctx, &variant_cls_type, &mut field_names); + impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names); field_getters.push(variant_match_args); @@ -1477,10 +1468,6 @@ fn complex_enum_tuple_variant_new<'a>( let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>); let args = { - let mut no_pyo3_attrs = vec![]; - let _attrs = - crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; - let mut args = vec![FnArg::Py(PyArg { name: &arg_py_ident, ty: &arg_py_type, @@ -1497,7 +1484,16 @@ fn complex_enum_tuple_variant_new<'a>( } args }; - let signature = crate::pyfunction::FunctionSignature::from_arguments(args)?; + + let signature = if let Some(constructor) = variant.options.constructor { + crate::pyfunction::FunctionSignature::from_arguments_and_attribute( + args, + constructor.into_signature(), + )? + } else { + crate::pyfunction::FunctionSignature::from_arguments(args)? + }; + let spec = FnSpec { tp: crate::method::FnType::FnNew, name: &format_ident!("__pymethod_constructor__"), diff --git a/pytests/src/enums.rs b/pytests/src/enums.rs index 3fa810b9042..964f0d431c3 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -93,6 +93,8 @@ enum SimpleTupleEnum { #[pyclass] pub enum TupleEnum { + #[pyo3(constructor = (_0 = 1, _1 = 1.0, _2 = true))] + FullWithDefault(i32, f64, bool), Full(i32, f64, bool), EmptyTuple(), } @@ -100,6 +102,7 @@ pub enum TupleEnum { #[pyfunction] pub fn do_tuple_stuff(thing: &TupleEnum) -> TupleEnum { match thing { + TupleEnum::FullWithDefault(a, b, c) => TupleEnum::FullWithDefault(*a, *b, *c), TupleEnum::Full(a, b, c) => TupleEnum::Full(*a, *b, *c), TupleEnum::EmptyTuple() => TupleEnum::EmptyTuple(), } diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index a7feb181ca2..cd4f7e124c9 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -150,6 +150,7 @@ def test_tuple_enum_variant_constructors(): @pytest.mark.parametrize( "variant", [ + enums.TupleEnum.FullWithDefault(), enums.TupleEnum.Full(42, 3.14, False), enums.TupleEnum.EmptyTuple(), ], @@ -158,6 +159,13 @@ def test_tuple_enum_variant_subclasses(variant: enums.TupleEnum): assert isinstance(variant, enums.TupleEnum) +def test_tuple_enum_defaults(): + variant = enums.TupleEnum.FullWithDefault() + assert variant._0 == 1 + assert variant._1 == 1.0 + assert variant._2 is True + + def test_tuple_enum_field_getters(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) assert tuple_variant._0 == 42 diff --git a/pytests/tests/test_enums_match.py b/pytests/tests/test_enums_match.py index bf721026308..6c4b5f6aa07 100644 --- a/pytests/tests/test_enums_match.py +++ b/pytests/tests/test_enums_match.py @@ -65,9 +65,6 @@ def test_complex_enum_pyfunction_in_out(variant: enums.ComplexEnum): enums.ComplexEnum.MultiFieldStruct(42, 3.14, True), ], ) -@pytest.mark.skip( - reason="__match_args__ is not supported for struct enums yet. TODO : Open an issue" -) def test_complex_enum_partial_match(variant: enums.ComplexEnum): match variant: case enums.ComplexEnum.MultiFieldStruct(a): From 7dd1a9306b3e4c09685d519bb0abdbcf3ab8f787 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Tue, 14 May 2024 08:29:46 +0200 Subject: [PATCH 23/24] formatting suggestion --- pyo3-macros-backend/src/pyclass.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 9d80b78d42d..38124db7d33 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -973,7 +973,7 @@ fn impl_complex_enum( } Ok(quote! { - #pytypeinfo + #pytypeinfo #pyclass_impls From 6d7596e61195b66b9e32067750c41c9f24d66333 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Thu, 16 May 2024 23:24:28 +0200 Subject: [PATCH 24/24] fix : - clippy beta - better compile error (+related doc and test) --- guide/src/class.md | 2 +- pyo3-macros-backend/src/pyclass.rs | 5 ++--- tests/ui/invalid_pyclass_enum.stderr | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index d287a20183c..57a5cf6d467 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -1183,7 +1183,7 @@ enum BadSubclass { An enum is complex if it has any non-unit (struct or tuple) variants. -Currently PyO3 supports only struct and tuple variants in a complex enum. Support for unit variants is planned. +PyO3 supports only struct and tuple variants in a complex enum. Unit variants aren't supported at present (the recommendation is to use an empty tuple enum instead). PyO3 adds a class attribute for each variant, which may be used to construct values and in match patterns. PyO3 also provides getter methods for all fields of each variant. diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 38124db7d33..47c52c84518 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -505,7 +505,7 @@ impl<'a> PyClassComplexEnum<'a> { Fields::Unit => { bail_spanned!(variant.span() => format!( "Unit variant `{ident}` is not yet supported in a complex enum\n\ - = help: change to a struct variant with no fields: `{ident} {{ }}`\n\ + = help: change to an empty tuple variant instead: `{ident}()`\n\ = note: the enum is complex because of non-unit variant `{witness}`", ident=ident, witness=witness)) } @@ -530,8 +530,7 @@ impl<'a> PyClassComplexEnum<'a> { let fields = types .unnamed .iter() - .enumerate() - .map(|(_i, field)| PyClassEnumVariantUnnamedField { + .map(|field| PyClassEnumVariantUnnamedField { ty: &field.ty, span: field.span(), }) diff --git a/tests/ui/invalid_pyclass_enum.stderr b/tests/ui/invalid_pyclass_enum.stderr index a103d6d41db..7e3b6ffa425 100644 --- a/tests/ui/invalid_pyclass_enum.stderr +++ b/tests/ui/invalid_pyclass_enum.stderr @@ -17,7 +17,7 @@ error: #[pyclass] can't be used on enums without any variants | ^^ error: Unit variant `UnitVariant` is not yet supported in a complex enum - = help: change to a struct variant with no fields: `UnitVariant { }` + = help: change to an empty tuple variant instead: `UnitVariant()` = note: the enum is complex because of non-unit variant `StructVariant` --> tests/ui/invalid_pyclass_enum.rs:21:5 |